12
.github/workflows/build_and_test.yml
vendored
@ -2,7 +2,7 @@ name: Build and Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches: [ main, dev ]
|
||||
pull_request:
|
||||
branches: [ main, dev ]
|
||||
|
||||
@ -63,5 +63,11 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Windows Build
|
||||
run: cargo build --verbose
|
||||
- name: Windows Test
|
||||
run: cargo test --verbose
|
||||
# TODO: Figure out how to properly build stuff with clang
|
||||
#- name: Add clang path to $PATH env
|
||||
# if: runner.os == 'Windows'
|
||||
# run: echo "C:\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8
|
||||
#- name: Try if clang works
|
||||
# run: clang -v
|
||||
#- name: Windows Test
|
||||
# run: C:\Rust\.cargo\bin\cargo.exe test --verbose
|
||||
|
3
.gitignore
vendored
@ -18,3 +18,6 @@ perf.data.old
|
||||
|
||||
.vscode
|
||||
test.dict
|
||||
|
||||
# Ignore all built fuzzers
|
||||
fuzzer_*
|
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "fuzzers/qemufuzzer/qemu-fuzz"]
|
||||
path = fuzzers/qemufuzzer/qemu-fuzz
|
||||
url = git@github.com:AFLplusplus/qemu-fuzz.git
|
17
Cargo.toml
@ -8,9 +8,16 @@ debug = true
|
||||
members = [
|
||||
"libafl",
|
||||
"libafl_derive",
|
||||
|
||||
#example fuzzers
|
||||
"fuzzers/libfuzzer_libpng",
|
||||
"fuzzers/libfuzzer_libmozjpeg",
|
||||
"fuzzers/libfuzzer_libpng_cmpalloc",
|
||||
"libafl_cc",
|
||||
"libafl_targets",
|
||||
"libafl_frida",
|
||||
]
|
||||
default-members = [
|
||||
"libafl",
|
||||
"libafl_derive",
|
||||
"libafl_cc",
|
||||
"libafl_targets",
|
||||
]
|
||||
exclude = [
|
||||
"fuzzers",
|
||||
]
|
||||
|
73
README.md
@ -1,10 +1,41 @@
|
||||
# LibAFL, the fuzzer library.
|
||||
|
||||
<img align="right" src="https://github.com/AFLplusplus/Website/raw/master/static/logo_256x256.png" alt="AFL++ Logo">
|
||||
|
||||
Advanced Fuzzing Library - Slot your own fuzzers together and extend their features using Rust.
|
||||
|
||||
LibAFL is written and maintained by Andrea Fioraldi <andreafioraldi@gmail.com> and Dominik Maier <mail@dmnk.co>.
|
||||
|
||||
It is released as Open Source Software under the [Apache v2](LICENSE-APACHE) or [MIT](LICENSE-MIT) licenses.
|
||||
## Why LibAFL?
|
||||
|
||||
LibAFL gives you many of the benefits of an off-the-shelf fuzzer, while being completely customizable.
|
||||
Some highlight features currently include:
|
||||
- `multi platform`: LibAFL was confirmed to work on *Windows*, *MacOS*, *Linux*, and *Android* on *x86_64* and *aarch64*.
|
||||
- `portable`: `LibAFL` can be built in `no_std` mode. Inject LibAFL in obscure targets like embedded devices and hypervisors.
|
||||
- `adaptable`: You can replace each part of LibAFL. For example, `BytesInput` is just one potential form input:
|
||||
feel free to add an AST-based input for structured fuzzing, and more.
|
||||
- `scalable`: `Low Level Message Passing`, `LLMP` for short, allows LibAFL to scale almost linearly over cores, and via TCP to multiple machines!
|
||||
- `fast`: We do everything we can at compile time, keeping runtime overhead minimal.
|
||||
- `bring your own target`: We support binary-only modes, like Frida-Mode, as well as multiple compilation passes for sourced-based instrumentation. Of course it's easy to add custom instrumentation backends.
|
||||
- `usable`: We hope. But we'll let you be the judge. Enjoy LibAFL.
|
||||
|
||||
## Overview
|
||||
|
||||
LibAFL is a collection of reusable pieces of fuzzers, written in Rust.
|
||||
It is fast, multi-platform, no_std compatible, and scales over cores and machines.
|
||||
|
||||
It offers a main crate that provide building blocks for custom fuzzers, [libafl](./libafl), a library containing common code that can be used for targets instrumentation, [libafl_targets](./libafl_targets), and a library providing facilities to wrap compilers, [libafl_cc](./libafl_cc).
|
||||
|
||||
LibAFL offers integrations with popular instrumemntation frameworks. At the moment, the supported backends are:
|
||||
|
||||
+ SanitizerCoverage, in [libafl_targets](./libafl_targets)
|
||||
+ Frida, in [libafl_frida](./libafl_frida), by s1341 <github@shmarya.net> (Windows support is broken atm, it relies on [this upstream issue](https://github.com/meme/frida-rust/issues/9) to be fixed.)
|
||||
+ More to come (QEMU-mode, ...)
|
||||
|
||||
LibAFL offers integrations with popular instrumemntation frameworks too. At the moment, the supported backends are:
|
||||
|
||||
+ SanitizerCoverage, in [libafl_targets](./libafl_targets)
|
||||
+ Frida, in [libafl_frida](./libafl_frida), by s1341 <github@shmarya.net> (Windows support will be added soon)
|
||||
|
||||
## Getting started
|
||||
|
||||
@ -25,29 +56,51 @@ Build the library using
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
Build the documentation with
|
||||
Build the API documentation with
|
||||
|
||||
```
|
||||
cargo doc
|
||||
```
|
||||
|
||||
We collect example fuzzers in `./fuzzers`. They can be build using `cargo build --example [fuzzer_name] --release`.
|
||||
Browse the LibAFL book (WIP!) with (requires [mdbook](https://github.com/rust-lang/mdBook))
|
||||
|
||||
The best-tested fuzzer is `./fuzzers/libfuzzer_libpng`, a clone of libfuzzer using libafl for a libpng harness.
|
||||
See its readme [here](./fuzzers/libfuzzer_libpng/README.md).
|
||||
```
|
||||
cd docs && mdbook serve
|
||||
```
|
||||
|
||||
## The Core Concepts
|
||||
We collect all example fuzzers in [`./fuzzers`](./fuzzers/).
|
||||
Be sure to read their documentation (and source), this is *the natural way to get started!*
|
||||
|
||||
The entire library is based on some core concepts that we think can generalize Fuzz Testing.
|
||||
The best-tested fuzzer is [`./fuzzers/libfuzzer_libpng`](./fuzzers/libfuzzer_libpng), a multicore libfuzzer-like fuzzer using LibAFL for a libpng harness.
|
||||
|
||||
We're still working on extending the documentation.
|
||||
## Resources
|
||||
|
||||
In the meantime, you can watch the Video from last year's RC3, here:
|
||||
+ [Installation guide](./docs/src/getting_started/setup.md)
|
||||
|
||||
[](http://www.youtube.com/watch?v=3RWkT1Q5IV0 "Fuzzers Like LEGO")
|
||||
+ Our RC3 [talk](http://www.youtube.com/watch?v=3RWkT1Q5IV0 "Fuzzers Like LEGO") explaining the core concepts
|
||||
|
||||
+ [Online API documentation](https://docs.rs/libafl/)
|
||||
|
||||
+ The LibAFL book (very WIP) [online](https://aflplus.plus/libafl-book) or in the [repo](./docs/src/)
|
||||
|
||||
## Contributing
|
||||
|
||||
Check the [TODO.md](./TODO.md) file for features that we plan to support.
|
||||
|
||||
For bugs, feel free to open issues or contact us directly. Thank you for your support. <3
|
||||
|
||||
#### License
|
||||
|
||||
<sup>
|
||||
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
||||
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
|
||||
</sup>
|
||||
|
||||
<br>
|
||||
|
||||
<sub>
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
|
||||
be dual licensed as above, without any additional terms or conditions.
|
||||
</sub>
|
||||
|
||||
|
22
TODO.md
@ -1,22 +1,26 @@
|
||||
# TODOs
|
||||
|
||||
- [x] ~~Minset corpus scheduler~~ still doc missing
|
||||
- [ ] Win32 shared mem and crash handler to have Windows in-process executor
|
||||
- [x] Other feedbacks examples (e.g. maximize allocations to spot OOMs)
|
||||
- [ ] Conditional composition of feedbacks (issue #24)
|
||||
- [ ] Other objectives examples (e.g. execution of a given program point)
|
||||
- [ ] Objective-Specific Corpuses (named per objective)
|
||||
- [x] A macro crate with derive directives (e.g. for SerdeAny impl).
|
||||
- [ ] Good documentation
|
||||
- [ ] LLMP brotli compression
|
||||
- [ ] LLMP compression
|
||||
- [ ] AFL-Style Forkserver Executor
|
||||
- [x] Restarting EventMgr could use forks on unix
|
||||
- [ ] Android Ashmem support
|
||||
- [ ] Restart Count in Fuzzing Loop
|
||||
- [ ] LAIN / structured fuzzing example
|
||||
- [ ] Errors in the Fuzzer should exit the fuzz run
|
||||
- [ ] More informative outpus, deeper introspection (stats, what mutation did x, etc.)
|
||||
- [x] Timeouts for executors
|
||||
- [ ] Timeout handling for llmp clients (no ping for n seconds -> treat as disconnected)
|
||||
- [ ] LLMP Cross Machine Link (2 brokers connected via TCP)
|
||||
- [ ] "Launcher" example that spawns broker + n clients
|
||||
- [ ] Heap for signal handling (bumpallo or llmp directly?)
|
||||
- [ ] Frida support for Windows
|
||||
- [ ] QEMU based instrumentation
|
||||
- [ ] AFL++ LLVM passes in libafl_cc
|
||||
- [x] Minset corpus scheduler
|
||||
- [x] Win32 shared mem and crash handler to have Windows in-process executor
|
||||
- [x] Other feedbacks examples (e.g. maximize allocations to spot OOMs)
|
||||
- [x] A macro crate with derive directives (e.g. for SerdeAny impl).
|
||||
- [x] Restarting EventMgr could use forks on Unix
|
||||
- [x] Android Ashmem support
|
||||
- [x] Errors in the Fuzzer should exit the fuzz run
|
||||
- [x] Timeouts for executors (WIP on Windows)
|
||||
|
25
clippy.sh
Executable file
@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
# Clippy checks
|
||||
cargo clean -p libafl
|
||||
RUST_BACKTRACE=full cargo clippy --all -- \
|
||||
-D clippy::pedantic \
|
||||
-W clippy::cast_sign_loss \
|
||||
-W clippy::similar-names \
|
||||
-W clippy::cast_ptr_alignment \
|
||||
-W clippy::cast_possible_wrap \
|
||||
-W clippy::unused_self \
|
||||
-W clippy::too_many_lines \
|
||||
-A missing-docs \
|
||||
-A clippy::doc_markdown \
|
||||
-A clippy::must-use-candidate \
|
||||
-A clippy::type_repetition_in_bounds \
|
||||
-A clippy::missing-errors-doc \
|
||||
-A clippy::cast-possible-truncation \
|
||||
-A clippy::used-underscore-binding \
|
||||
-A clippy::ptr-as-ptr \
|
||||
-A clippy::missing-panics-doc \
|
||||
-A clippy::missing-docs-in-private-items \
|
||||
-A clippy::unseparated-literal-suffix \
|
||||
-A clippy::module-name-repetitions \
|
||||
-A clippy::unreadable-literal \
|
||||
-A clippy::if-not-else \
|
1
docs/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
book
|
13
docs/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# LibAFL Documentation Book
|
||||
|
||||
This project contains the out-of-source LibAFL documentation as a book.
|
||||
|
||||
Here you can find tutorials, examples, and detailed explanations.
|
||||
|
||||
For the API documentation instead, run `cargo doc` in the LibAFl root folder.
|
||||
|
||||
## Usage
|
||||
|
||||
To build this book, you need [mdBook](https://github.com/rust-lang/mdBook).
|
||||
|
||||
`mdbook build` to build, `mdbook serve` to serve the book locally.
|
6
docs/book.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[book]
|
||||
authors = ["Andrea Fioraldi", "Dominik Maier"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "The LibAFL Fuzzing Library"
|
21
docs/src/SUMMARY.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Summary
|
||||
|
||||
[The LibAFL Fuzzing Library](./libafl.md)
|
||||
|
||||
[Introduction](./introduction.md)
|
||||
|
||||
- [Getting Started](./getting_started/getting_started.md)
|
||||
- [Setup](./getting_started/setup.md)
|
||||
- [Build](./getting_started/build.md)
|
||||
- [Crates](./getting_started/crates.md)
|
||||
|
||||
- [Baby Fuzzer](./baby_fuzzer.md)
|
||||
|
||||
- [Design](./design/design.md)
|
||||
- [Core Concepts](./design/core_concepts.md)
|
||||
- [Architecture](./design/architecture.md)
|
||||
|
||||
- [Understanding Metadata](./medatata/metadata.md)
|
||||
- [Definition](./medatata/definition.md)
|
||||
- [(De)Serialization](./medatata/de_serialization.md)
|
||||
- [Usage](./medatata/usage.md)
|
1
docs/src/baby_fuzzer.md
Normal file
@ -0,0 +1 @@
|
||||
# Baby Fuzzer
|
13
docs/src/design/architecture.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Architecture
|
||||
|
||||
The LibAFL architecture is built around some entities to allow code reuse and low-cost abstractions.
|
||||
|
||||
Initially, we started thinking to implement LibAFL in an Object Oriented language, such C++. When we landed to Rust, we immediately changed our idea as we realized that, while Rust allow a sort of OOP pattern, we can build the library using a more sane approach like the one described in [this blogpost](https://kyren.github.io/2018/09/14/rustconf-talk.html) about game design in Rust.
|
||||
|
||||
The LibAFL code reuse meachanism is so based on components rather than sub-classes, but there are still some OOP patterns in the library.
|
||||
|
||||
Thinking about similar fuzzers, you can observe that most of the times the data structures that are modified are the ones related to testcases and the fuzzer global state.
|
||||
|
||||
Beside the entities described previously, we introduce the Testcase and State entities. The Testcase is a container for an Input stored in the Corpus and its metadata (so, in the implementation, the Corpus stores Testcases) and the State contains all the metadata that are evolved while running the fuzzer, Corpus included.
|
||||
|
||||
|
86
docs/src/design/core_concepts.md
Normal file
@ -0,0 +1,86 @@
|
||||
# Core Concepts
|
||||
|
||||
LibAFL is designed around some core concepts that we think can effectively abstract most of the other fuzzers designs.
|
||||
|
||||
Here, we discuss these concepts and provide some examples related to other fuzzers.
|
||||
|
||||
TODO add links to trait definitions in docs.rs
|
||||
|
||||
## Observer
|
||||
|
||||
An Observer, or Observation Channel, is an entity that provides an information observed during the execution of the program under test to the fuzzer.
|
||||
|
||||
The information contained in the Observer is not preserved cross executions.
|
||||
|
||||
As an example, the coverage shared map filled during the execution to report the executed edges used by fuzzers such as AFL and HoggFuzz can be considered an Observation Channel.
|
||||
This information is not preserved accros runs and it is an observation of a dynamic property of the program.
|
||||
|
||||
## Executor
|
||||
|
||||
In different fuzzers, the concept of executing the program under test each run is now always the same.
|
||||
For instance, for in-memory fuzzers like libFuzzer an execution is a call to an harness function, for hypervisor-based fuzzers like [kAFL](https://github.com/IntelLabs/kAFL) instead an entire operating system is started from a snapshot each run.
|
||||
|
||||
In our model, an Executor is the entity that defines not only how to execute the target, but all the volatile operations that are related to just a single run of the target.
|
||||
|
||||
So the Executor is for instance reponsible to inform the program about the input that the fuzzer wants to use in the run, writing to a memory location for instance or passing it as a parameter to the harness function.
|
||||
|
||||
It also holds a set of Observers, as thay are related to just a single run of the target.
|
||||
|
||||
## Feedback
|
||||
|
||||
The Feedback is an entity that classify the outcome of an execution of the program under test as interesting or not.
|
||||
Tipically, if an exeuction is interesting, the corresponding input used to feed the target program is added to a corpus.
|
||||
|
||||
Most of the times, the notion of Feedback is deeply linked to the Observer, but they are different concepts.
|
||||
|
||||
The Feedback, in most of the cases, process the information reported by one or more observer to decide if the execution is interesting.
|
||||
The concept of "interestingness" is abstract, but tipically it is related to a novelty search (i.e. interesting inputs are those that reach a previosly unseen edge in the control flow graph).
|
||||
|
||||
As an example, given an Observer that reports all the size of memory allocations, a maximization Feedback can be used to maximize these sizes to sport patological inputs in terms of memory consumption.
|
||||
|
||||
## Input
|
||||
|
||||
Formally, the input of a program is the data taken from external sources and that affect the program behaviour.
|
||||
|
||||
In our model of an abstarct fuzzer, we define the Input as the internal representation of the program input (or a part of it).
|
||||
|
||||
In the straightforward case, the input of the program is a byte array and in fuzzers such as AFL we store an manipulate exaclty these byte arrays.
|
||||
|
||||
But it is not always the case. A program can expect inputs that are not byte arrays (e.g. a sequence of syscalls) and the fuzzer does not represent the Input in the same way that the program consume it.
|
||||
|
||||
In case of a grammar fuzzer for instance, the Input is generally an Abstract Syntax Tree because it is a data structure that can be easily manipulated while maintaining the validity, but the program expects a byte array as input so, just before the execution, the tree is serialized to a sequence of bytes.
|
||||
|
||||
## Corpus
|
||||
|
||||
The Corpus is where testcases are stored. A Testcase is defined as an Input and a set of related metadata like execution time for instance.
|
||||
|
||||
For instance, a Corpus can store testcases on disk, or in memory, or implement a cache to speedup on disk storage.
|
||||
|
||||
Usually, a testcase is added to the Corpus when it is considered as interesting.
|
||||
|
||||
## Mutator
|
||||
|
||||
The Mutator is an entitiy that takes one or more Inputs and generates a new derived one.
|
||||
|
||||
Mutators can be composed and they are generally linked to a specific Input type.
|
||||
|
||||
There can be, for instance, a Mutator that applies more than a single type of mutation on the input. Consider a generic Mutator for a byte stream, bit flip is just one of the possible mutations but not the single one, there is also, for instance, the random replacement of a byte of the copy of a chunk.
|
||||
|
||||
This Mutator will simple schedule the application of some other Mutators.
|
||||
|
||||
## Generator
|
||||
|
||||
A Generator is a component designed to generate an Input from scratch.
|
||||
|
||||
Tipically, a random generator is used to generate random inputs.
|
||||
|
||||
Generators are traditionally less used in Feedback-driven Fuzzing, but there are exceptions, like Nautilus, that uses a Grammar generator to create the initial corpus and a sub-tree Generator as a mutation of its grammar Mutator.
|
||||
|
||||
## Stage
|
||||
|
||||
A Stage is an entity that operates on a single Input got from the Corpus.
|
||||
|
||||
For instamce, a Mutational Stage, given an input of the corpus, applies a Mutator and executes the generated input one or more time. How many times this has to be done can be scheduled, AFL for instance use a performance score of the input to choose how many times the havoc mutator should be invoked. This can depends also on other parameters, for instance, the length of the input if we want to just apply a sequential bitflip, or be a fixed value.
|
||||
|
||||
A stage can be also an analysis stage, for instance, the Colorization stage of Redqueen that aims to introduce more entropy in a testcase or the Trimming stage of AFL that aims to reduce the size of a testcase.
|
||||
|
3
docs/src/design/design.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Design
|
||||
|
||||
In this chapter, we introduce the abstract Core Concepts behind LibAFL, we then discuss how we designed the library to take into account these concepts while allowing code reuse and extensibility.
|
25
docs/src/getting_started/build.md
Normal file
@ -0,0 +1,25 @@
|
||||
# Build
|
||||
|
||||
LibAFL, as most of the Rust projects, can be built using `cargo` from the root directory of the project with:
|
||||
|
||||
```sh
|
||||
$ cargo build --release
|
||||
```
|
||||
|
||||
Note that the `--release` flag is optional for development, but you needed to add it to fuzzing at a decent speed.
|
||||
Slowdowns of 10x or more are not uncommon for Debug builds.
|
||||
|
||||
The LibAFL repository is composed of multiple crates.
|
||||
The top-level Cargo.toml is the workspace file grouping these crates.
|
||||
Calling `cargo build` from the root directory will compile all crates in the workspace.
|
||||
|
||||
## Build Example Fuzzers
|
||||
|
||||
We group example fuzzers in the `./fuzzers` directory of the LibAFL repository.
|
||||
The directory contains a set of crates that are not part of the workspace.
|
||||
|
||||
Each of these example fuzzers uses particular features of LibAFL, sometimes combined with different instrumentation backends (e.g. [SanitizerCoverage](https://clang.llvm.org/docs/SanitizerCoverage.html), [Frida](https://frida.re/), ...).
|
||||
|
||||
You can use these crates as examples and as skeletons for custom fuzzers with similar featuresets.
|
||||
|
||||
To build an example fuzzer you have to invoke cargo from its respective folder (`fuzzers/[FUZZER_NAME]).
|
40
docs/src/getting_started/crates.md
Normal file
@ -0,0 +1,40 @@
|
||||
# Crates
|
||||
|
||||
LibAFL is composed by different crates.
|
||||
Each one has its self-contained purpose, and the user may not need to use all of them in its project.
|
||||
|
||||
Following the naming convention of the folders in the project's root, they are:
|
||||
|
||||
### libafl
|
||||
|
||||
This is the main crate that contains all the components needed to build a fuzzer.
|
||||
|
||||
This crate has the following feature flags:
|
||||
|
||||
- std, that enables the parts of the code that use the Rust standard library. Without this flags, libafl is no_std.
|
||||
- derive, that enables the usage of the `derive(...)` macros defined in libafl_derive from libafl.
|
||||
|
||||
By default, std and derive are both set.
|
||||
|
||||
### libafl_derive
|
||||
|
||||
This a proc-macro crate paired with the libafl crate.
|
||||
|
||||
At the moment, it just expose the `derive(SerdeAny)` macro that can be used to define metadata structs.
|
||||
|
||||
### libafl_targets
|
||||
|
||||
This crate that exposes, under feature flags, pieces of code to interact with targets
|
||||
|
||||
Currently, the supported flags are:
|
||||
|
||||
- pcguard_edges, that defines the SanitizerCoverage trace-pc-guard hooks to track the executed edges in a map.
|
||||
- pcguard_hitcounts, that defines the SanitizerCoverage trace-pc-guard hooks to track the executed edges with the hitcounts (like AFL) in a map.
|
||||
- libfuzzer, that expose a compatibility layer with libFuzzer style harnesses.
|
||||
- value_profile, that defines the SanitizerCoverage trace-cmp hooks to track the matching bits of each comparison in a map.
|
||||
|
||||
### libafl_cc
|
||||
|
||||
This is a library that provides some utils to wrap compilers and create source level fuzzers.
|
||||
|
||||
At the moment, only the Clang compiler is supported.
|
4
docs/src/getting_started/getting_started.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Getting Started
|
||||
|
||||
To start using LibAFL, there are some first steps to do. In this chapter, we will
|
||||
discuss how to download LibAFL and build with `cargo`, how are structured its crates and the purpose of each crate.
|
58
docs/src/getting_started/setup.md
Normal file
@ -0,0 +1,58 @@
|
||||
# Setup
|
||||
|
||||
The first step is to download LibAFL and all its dependencies that are not automatically installed with `cargo`.
|
||||
|
||||
> ### Command Line Notation
|
||||
>
|
||||
> In this chapter and throughout the book, we’ll show some commands used in the
|
||||
> terminal. Lines that you should enter in a terminal all start with `$`. You
|
||||
> don’t need to type in the `$` character; it indicates the start of each
|
||||
> command. Lines that don’t start with `$` typically show the output of the
|
||||
> previous command. Additionally, PowerShell-specific examples will use `>`
|
||||
> rather than `$`.
|
||||
|
||||
The easiest way to download LibAFL is using `git`.
|
||||
|
||||
```sh
|
||||
$ git clone git@github.com:AFLplusplus/LibAFL.git
|
||||
```
|
||||
|
||||
You can alternatively, on a UNIX-like machine, download a compressed archive and extract with:
|
||||
|
||||
```sh
|
||||
$ wget https://github.com/AFLplusplus/LibAFL/archive/main.tar.gz
|
||||
$ tar xvf LibAFL-main.tar.gz
|
||||
$ rm LibAFL-main.tar.gz
|
||||
$ ls LibAFL-main # this is the extracted folder
|
||||
```
|
||||
|
||||
## Clang installation
|
||||
|
||||
One of the external dependencies of LibAFL is the Clang C/C++ compiler.
|
||||
While most of the code is in pure Rust, we still need a C compiler because Rust stable
|
||||
still does not support features that we need such as weak linking and LLVM builtins linking,
|
||||
and so we use C to expose the missing functionalities to our Rust codebase.
|
||||
|
||||
In addition, if you want to perform source-level fuzz testing of C/C++ applications,
|
||||
you will likely need Clang with its instrumentation options to compile the programs
|
||||
under test.
|
||||
|
||||
You can download and build the LLVM source tree, Clang included, following the steps
|
||||
explained [here](https://clang.llvm.org/get_started.html).
|
||||
|
||||
Alternatively, on Linux, you can use your distro's package manager to get Clang,
|
||||
but these packages are not always updated, so we suggest you to use the
|
||||
Debian/Ubuntu prebuilt packages from LLVM that are available using their [official repository](https://apt.llvm.org/).
|
||||
|
||||
For Miscrosoft Windows, you can download the [installer package](https://llvm.org/builds/) that LLVM generates periodically.
|
||||
|
||||
Despite that Clang is the default C compiler on macOS, we discourage the use of the build shipped by Apple and encourage
|
||||
the installation from `brew` or direclty a fresh build from the source code.
|
||||
|
||||
## Rust installation
|
||||
|
||||
If you don't have Rust installed, you can easily follow the steps described [here](https://www.rust-lang.org/tools/install)
|
||||
to install it on any supported system.
|
||||
|
||||
We suggest to install Clang and LLVM first.
|
||||
|
29
docs/src/introduction.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Introduction
|
||||
|
||||
Fuzzers are important assets in the pockets of security researchers and developers alike.
|
||||
A wide range of cool state-of-the-art tools like [AFL++](https://github.com/AFLplusplus/AFLplusplus), [libFuzzer](https://llvm.org/docs/LibFuzzer.html) or [honggfuzz](https://github.com/google/honggfuzz) are available to users. They do their job in a very effective way, finding thousands of bugs.
|
||||
|
||||
From the power user perspective, however, these tools are limited.
|
||||
Their design does not treat extensibility as a first-class citizen.
|
||||
Usually, a fuzzer developer can choose to either fork one of these existing tools, or to create a new fuzzer from scratch.
|
||||
In any case, researchers end up with tons of fuzzers, all of which are incompatible with each other.
|
||||
Their outstanding features can not just be combined for new projects.
|
||||
Instead, we keep reinventing the wheel and may completely miss out on features that are complex to reimplement.
|
||||
|
||||
Here comes LibAFL, a library that IS NOT a fuzzer, but a collection of reusable pieces of fuzzers, written in Rust.
|
||||
LibAFL helps you develop your own custom fuzzer, tailored for your specific needs.
|
||||
Be it a specific target, a particular instrumentation backend, or a custom mutator, you can leverage existing bits and pieces to craft the fastest and most efficient fuzzer you can envision.
|
||||
|
||||
## Why LibAFL?
|
||||
|
||||
LibAFL gives you many of the benefits of an off-the-shelf fuzzer, while being completely customizable.
|
||||
Some highlight features currently include:
|
||||
- `multi platform`: LibAFL works pretty much anywhere you can find a Rust compiler for. We already used it on *Windows*, *Android*, *MacOS*, and *Linux*, on *x86_64*, *aarch64*, ...
|
||||
- `portable`: `LibAFL` can be built in `no_std` mode. This means it does not require a specific OS-dependent runtime to function. Define an allocator and a way to map pages, you should be good to inject LibAFL in obscure targets like embedded devices, hypervisors, or maybe even WebAssembly?
|
||||
- `adaptable`: Given year of experience fine-tuning *AFLplusplus* and our academic fuzzing background, we could incorporate recent fuzzing trends into LibAFL's deign and make it future-proof.
|
||||
To give an example, as opposed to old-skool fuzzers, a `BytesInput` is just one of the potential forms of inputs:
|
||||
feel free to use and mutate an Abstract Syntax Tree instead, for structured fuzzing.
|
||||
- `scalable`: As part of LibAFL, we developed `Low Level Message Passing`, `LLMP` for short, which allows LibAFL to scale almost linearly over cores. That is, if you chose to use this feature - it is your fuzzer, after all. Scaling to multiple machines over TCP is on the near road-map.
|
||||
- `fast`: We do everything we can at compiletime so that the runtime overhead is as minimal as it can get.
|
||||
- `bring your own target`: We support binary-only modes, like Frida-Mode with ASAN and CmpLog, as well as multiple compilation passes for sourced-based instrumentation, and of course supoprt custom instrumentation.
|
||||
- `usable`: This one is on you to decide. Dig right in!
|
9
docs/src/libafl.md
Normal file
@ -0,0 +1,9 @@
|
||||
# The LibAFL Fuzzing Library
|
||||
|
||||
*by Andrea Fioraldi and Dominik Maier*
|
||||
|
||||
This version of the LibAFL book is coupled with the release 1.0 beta of the library.
|
||||
|
||||
This document is still work-in-progress and incomplete. The structure and the concepts explained here are subject to change in future revisions, as the structure of LibAFL itself will evolve.
|
||||
|
||||
The HTML version of this book is available online at PLACEHOLDER and offline from the LibAFL repository in the docs/ folder.
|
3
docs/src/medatata/de_serialization.md
Normal file
@ -0,0 +1,3 @@
|
||||
# (De)Serialization
|
||||
|
||||
TODO describe the SerdeAny registry
|
19
docs/src/medatata/definition.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Definition
|
||||
|
||||
A metadata in LibAFL is a self contained structure that holds associated data to the State or to a Testcase.
|
||||
|
||||
In terms of code, a metadata can be defined as a Rust struct registered in the SerdeAny register.
|
||||
|
||||
```rust
|
||||
use libafl::SerdeAny;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, SerdeAny)]
|
||||
pub struct MyMetadata {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The struct must be static, so it cannot holds references to borrowed objects.
|
||||
|
||||
|
3
docs/src/medatata/metadata.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Understanding Metadata
|
||||
|
||||
In this chapter, we discuss in depth the metadata system of LibAFL and its usage.
|
3
docs/src/medatata/usage.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Usage
|
||||
|
||||
TODO describe the HasMetadata interface
|
22
fuzzers/baby_fuzzer/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "baby_fuzzer"
|
||||
version = "0.1.0"
|
||||
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[dependencies]
|
||||
libafl = { path = "../../libafl/" }
|
7
fuzzers/baby_fuzzer/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Baby fuzzer
|
||||
|
||||
This is a minimalistic example about how to create a libafl based fuzzer.
|
||||
|
||||
It runs on a single core until a crash occurs and then exits.
|
||||
|
||||
The tested program is a simple Rust function without any instrumentation.
|
99
fuzzers/baby_fuzzer/src/main.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use libafl::{
|
||||
bolts::tuples::tuple_list,
|
||||
corpus::{InMemoryCorpus, OnDiskCorpus, QueueCorpusScheduler},
|
||||
events::SimpleEventManager,
|
||||
executors::{inprocess::InProcessExecutor, ExitKind},
|
||||
feedbacks::{CrashFeedback, MaxMapFeedback},
|
||||
fuzzer::{Fuzzer, StdFuzzer},
|
||||
generators::RandPrintablesGenerator,
|
||||
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
|
||||
observers::StdMapObserver,
|
||||
stages::mutational::StdMutationalStage,
|
||||
state::State,
|
||||
stats::SimpleStats,
|
||||
utils::{current_nanos, StdRand},
|
||||
};
|
||||
|
||||
// Coverage map with explicit assignments due to the lack of instrumentation
|
||||
static mut SIGNALS: [u8; 16] = [0; 16];
|
||||
|
||||
fn signals_set(idx: usize) {
|
||||
unsafe { SIGNALS[idx] = 1 };
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
// The closure that we want to fuzz
|
||||
let mut harness = |buf: &[u8]| {
|
||||
signals_set(0);
|
||||
if buf.len() > 0 && buf[0] == 'a' as u8 {
|
||||
signals_set(1);
|
||||
if buf.len() > 1 && buf[1] == 'b' as u8 {
|
||||
signals_set(2);
|
||||
if buf.len() > 2 && buf[2] == 'c' as u8 {
|
||||
panic!("=)");
|
||||
}
|
||||
}
|
||||
}
|
||||
ExitKind::Ok
|
||||
};
|
||||
|
||||
// The Stats trait define how the fuzzer stats are reported to the user
|
||||
let stats = SimpleStats::new(|s| println!("{}", s));
|
||||
|
||||
// The event manager handle the various events generated during the fuzzing loop
|
||||
// such as the notification of the addition of a new item to the corpus
|
||||
let mut mgr = SimpleEventManager::new(stats);
|
||||
|
||||
// Create an observation channel using the siganls map
|
||||
let observer =
|
||||
StdMapObserver::new("signals", unsafe { &mut SIGNALS }, unsafe { SIGNALS.len() });
|
||||
|
||||
// create a State from scratch
|
||||
let mut state = State::new(
|
||||
// RNG
|
||||
StdRand::with_seed(current_nanos()),
|
||||
// Corpus that will be evolved, we keep it in memory for performance
|
||||
InMemoryCorpus::new(),
|
||||
// Feedbacks to rate the interestingness of an input
|
||||
tuple_list!(MaxMapFeedback::new_with_observer(&observer)),
|
||||
// Corpus in which we store solutions (crashes in this example),
|
||||
// on disk so the user can get them after stopping the fuzzer
|
||||
OnDiskCorpus::new(PathBuf::from("./crashes")).unwrap(),
|
||||
// Feedbacks to recognize an input as solution
|
||||
tuple_list!(CrashFeedback::new()),
|
||||
);
|
||||
|
||||
// Setup a basic mutator with a mutational stage
|
||||
let mutator = StdScheduledMutator::new(havoc_mutations());
|
||||
let stage = StdMutationalStage::new(mutator);
|
||||
|
||||
// A fuzzer with just one stage
|
||||
let mut fuzzer = StdFuzzer::new(tuple_list!(stage));
|
||||
|
||||
// A queue policy to get testcasess from the corpus
|
||||
let scheduler = QueueCorpusScheduler::new();
|
||||
|
||||
// Create the executor for an in-process function with just one observer
|
||||
let mut executor = InProcessExecutor::new(
|
||||
"in-process(signals)",
|
||||
&mut harness,
|
||||
tuple_list!(observer),
|
||||
&mut state,
|
||||
&mut mgr,
|
||||
)
|
||||
.expect("Failed to create the Executor".into());
|
||||
|
||||
// Generator of printable bytearrays of max size 32
|
||||
let mut generator = RandPrintablesGenerator::new(32);
|
||||
|
||||
// Generate 8 initial inputs
|
||||
state
|
||||
.generate_initial_inputs(&mut executor, &mut generator, &mut mgr, &scheduler, 8)
|
||||
.expect("Failed to generate the initial corpus".into());
|
||||
|
||||
fuzzer
|
||||
.fuzz_loop(&mut state, &mut executor, &mut mgr, &scheduler)
|
||||
.expect("Error in the fuzzing loop".into());
|
||||
}
|
1
fuzzers/frida_libpng/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
libpng-*
|
35
fuzzers/frida_libpng/Cargo.toml
Normal file
@ -0,0 +1,35 @@
|
||||
[package]
|
||||
name = "frida_libpng"
|
||||
version = "0.1.0"
|
||||
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
|
||||
[features]
|
||||
default = ["std", "frida"]
|
||||
std = []
|
||||
frida = ["frida-gum", "frida-gum-sys"]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[build-dependencies]
|
||||
cc = { version = "1.0", features = ["parallel"] }
|
||||
num_cpus = "1.0"
|
||||
which = "4.1"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libafl = { path = "../../libafl/", features = [ "std" ] } #, "llmp_small_maps", "llmp_debug"]}
|
||||
capstone = "0.8.0"
|
||||
frida-gum = { version = "0.4", optional = true, features = [ "auto-download", "event-sink", "invocation-listener"] }
|
||||
frida-gum-sys = { version = "0.2.4", optional = true, features = [ "auto-download", "event-sink", "invocation-listener"] }
|
||||
libafl_frida = { path = "../../libafl_frida", version = "0.1.0" }
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2"
|
||||
libloading = "0.7.0"
|
||||
num-traits = "0.2.14"
|
||||
rangemap = "0.1.10"
|
||||
seahash = "4.1.0"
|
@ -1,18 +1,15 @@
|
||||
# Libfuzzer for libpng (cmp+alloc)
|
||||
# Libfuzzer for libpng
|
||||
|
||||
This folder contains an example fuzzer for libpng, using LLMP for fast multi-process fuzzing and crash detection.
|
||||
To show off crash detection, we added a ud2 instruction to the harness, edit harness.cc if you want a non-crashing example.
|
||||
It has been tested on Linux.
|
||||
|
||||
The difference between the normal Libfuzzer for libpng example here is that this fuzzer is not just using edge coverage as feedback but also comparisons values (-value-profile like) and allocations sizes.
|
||||
This is an example how multiple feedbacks can be combined in a fuzzer.
|
||||
|
||||
## Build
|
||||
|
||||
To build this example, run `cargo build --example libfuzzer_libpng_cmpalloc --release`.
|
||||
This will call (the build.rs)[./builld.rs], which in turn downloads a libpng archive from the web.
|
||||
To build this example, run `cargo build --example libfuzzer_libpng --release`.
|
||||
This will call (the build.rs)[./build.rs], which in turn downloads a libpng archive from the web.
|
||||
Then, it will link (the fuzzer)[./src/fuzzer.rs] against (the C++ harness)[./harness.cc] and the instrumented `libpng`.
|
||||
Afterwards, the fuzzer will be ready to run, from `../../target/examples/libfuzzer_libpng_cmpalloc`.
|
||||
Afterwards, the fuzzer will be ready to run, from `../../target/examples/libfuzzer_libpng`.
|
||||
|
||||
## Run
|
||||
|
@ -6,12 +6,28 @@ use std::{
|
||||
process::{exit, Command},
|
||||
};
|
||||
|
||||
use which::which;
|
||||
|
||||
const LIBPNG_URL: &str =
|
||||
"https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz";
|
||||
|
||||
fn build_dep_check(tools: &[&str]) {
|
||||
for tool in tools.into_iter() {
|
||||
println!("Checking for build tool {}...", tool);
|
||||
|
||||
match which(tool) {
|
||||
Ok(path) => println!("Found build tool {}", path.to_str().unwrap()),
|
||||
Err(_) => {
|
||||
println!("ERROR: missing build tool {}", tool);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if cfg!(windows) {
|
||||
println!("cargo:warning=Skipping libpng example on Windows");
|
||||
println!("cargo:warning=Skipping libpng frida example on Windows");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@ -19,22 +35,32 @@ fn main() {
|
||||
let cwd = env::current_dir().unwrap().to_string_lossy().to_string();
|
||||
let out_dir = out_dir.to_string_lossy().to_string();
|
||||
let out_dir_path = Path::new(&out_dir);
|
||||
std::fs::create_dir_all(&out_dir).expect(&format!("Failed to create {}", &out_dir));
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=../libfuzzer_runtime/rt.c",);
|
||||
println!("cargo:rerun-if-changed=harness.cc");
|
||||
|
||||
build_dep_check(&["clang", "clang++", "wget", "tar", "make"]);
|
||||
|
||||
let libpng = format!("{}/libpng-1.6.37", &out_dir);
|
||||
let libpng_path = Path::new(&libpng);
|
||||
let libpng_tar = format!("{}/libpng-1.6.37.tar.xz", &cwd);
|
||||
|
||||
// Enforce clang for its -fsanitize-coverage support.
|
||||
std::env::set_var("CC", "clang");
|
||||
std::env::set_var("CXX", "clang++");
|
||||
let clang = match env::var("CLANG_PATH") {
|
||||
Ok(path) => path,
|
||||
Err(_) => "clang".to_string(),
|
||||
};
|
||||
let clangpp = format!("{}++", &clang);
|
||||
std::env::set_var("CC", &clang);
|
||||
std::env::set_var("CXX", &clangpp);
|
||||
let ldflags = match env::var("LDFLAGS") {
|
||||
Ok(val) => val,
|
||||
Err(_) => "".to_string(),
|
||||
};
|
||||
|
||||
// println!("cargo:warning=output path is {}", libpng);
|
||||
if !libpng_path.is_dir() {
|
||||
if !Path::new(&libpng_tar).is_file() {
|
||||
println!("cargo:warning=Libpng not found, downloading...");
|
||||
@ -49,7 +75,7 @@ fn main() {
|
||||
}
|
||||
Command::new("tar")
|
||||
.current_dir(&out_dir_path)
|
||||
.arg("-xvf")
|
||||
.arg("xvf")
|
||||
.arg(&libpng_tar)
|
||||
.status()
|
||||
.unwrap();
|
||||
@ -59,19 +85,20 @@ fn main() {
|
||||
"--disable-shared",
|
||||
&format!("--host={}", env::var("TARGET").unwrap())[..],
|
||||
])
|
||||
.env("CC", "clang")
|
||||
.env("CXX", "clang++")
|
||||
.env("CC", &clang)
|
||||
.env("CXX", &clangpp)
|
||||
.env(
|
||||
"CFLAGS",
|
||||
"-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||
"-O3 -g -D_DEFAULT_SOURCE -fPIC -fno-omit-frame-pointer",
|
||||
)
|
||||
.env(
|
||||
"CXXFLAGS",
|
||||
"-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||
"-O3 -g -D_DEFAULT_SOURCE -fPIC -fno-omit-frame-pointer",
|
||||
)
|
||||
.env(
|
||||
"LDFLAGS",
|
||||
format!("-g -fPIE -fsanitize-coverage=trace-pc-guard {}", ldflags),
|
||||
//format!("-g -fPIE -fsanitize=address {}", ldflags),
|
||||
format!("-g -fPIE {}", ldflags),
|
||||
)
|
||||
.status()
|
||||
.unwrap();
|
||||
@ -81,29 +108,31 @@ fn main() {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
cc::Build::new()
|
||||
.file("../libfuzzer_runtime/rt.c")
|
||||
.compile("libfuzzer-sys");
|
||||
|
||||
cc::Build::new()
|
||||
.include(&libpng_path)
|
||||
let status = cc::Build::new()
|
||||
.cpp(true)
|
||||
.flag("-fsanitize-coverage=trace-pc-guard")
|
||||
// .define("HAS_DUMMY_CRASH", "1")
|
||||
.file("./harness.cc")
|
||||
.compile("libfuzzer-harness");
|
||||
|
||||
println!("cargo:rustc-link-search=native={}", &out_dir);
|
||||
println!("cargo:rustc-link-search=native={}/.libs", &libpng);
|
||||
println!("cargo:rustc-link-lib=static=png16");
|
||||
|
||||
//Deps for libpng: -pthread -lz -lm
|
||||
println!("cargo:rustc-link-lib=dylib=m");
|
||||
println!("cargo:rustc-link-lib=dylib=z");
|
||||
|
||||
//For the C++ harness
|
||||
//must by dylib for android
|
||||
println!("cargo:rustc-link-lib=dylib=stdc++");
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
.get_compiler()
|
||||
.to_command()
|
||||
.current_dir(&cwd)
|
||||
.arg("-I")
|
||||
.arg(&libpng)
|
||||
//.arg("-D")
|
||||
//.arg("HAS_DUMMY_CRASH=1")
|
||||
.arg("-fPIC")
|
||||
.arg("-shared")
|
||||
.arg("-O3")
|
||||
//.arg("-fomit-frame-pointer")
|
||||
.arg(if env::var("CARGO_CFG_TARGET_OS").unwrap() == "android" {
|
||||
"-static-libstdc++"
|
||||
} else {
|
||||
""
|
||||
})
|
||||
.arg("-o")
|
||||
.arg(format!("{}/libpng-harness.so", &out_dir))
|
||||
.arg("./harness.cc")
|
||||
.arg(format!("{}/.libs/libpng16.a", &libpng))
|
||||
.arg("-l")
|
||||
.arg("z")
|
||||
.status()
|
||||
.unwrap();
|
||||
assert!(status.success());
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
@ -79,6 +80,29 @@ void user_read_data(png_structp png_ptr, png_bytep data, size_t length) {
|
||||
|
||||
static const int kPngHeaderSize = 8;
|
||||
|
||||
extern "C" int afl_libfuzzer_init() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char * allocation = NULL;
|
||||
__attribute__((noinline))
|
||||
void func3( char * alloc) {
|
||||
printf("func3\n");
|
||||
if (random() % 5 == 0) {
|
||||
alloc[0xff] = 0xde;
|
||||
}
|
||||
}
|
||||
__attribute__((noinline))
|
||||
void func2() {
|
||||
allocation = (char*)malloc(0xff);
|
||||
printf("func2\n");
|
||||
func3(allocation);
|
||||
}
|
||||
__attribute__((noinline))
|
||||
void func1() {
|
||||
printf("func1\n");
|
||||
func2();
|
||||
}
|
||||
// Entry point for LibFuzzer.
|
||||
// Roughly follows the libpng book example:
|
||||
// http://www.libpng.org/pub/png/book/chapter13.html
|
||||
@ -87,6 +111,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
func1();
|
||||
|
||||
std::vector<unsigned char> v(data, data + size);
|
||||
if (png_sig_cmp(v.data(), 0, kPngHeaderSize)) {
|
||||
// not a PNG.
|
360
fuzzers/frida_libpng/src/fuzzer.rs
Normal file
@ -0,0 +1,360 @@
|
||||
//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts
|
||||
//! The example harness is built for libpng.
|
||||
|
||||
use libafl::{
|
||||
bolts::tuples::{tuple_list, Named},
|
||||
corpus::{
|
||||
ondisk::OnDiskMetadataFormat, Corpus, InMemoryCorpus,
|
||||
IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, QueueCorpusScheduler,
|
||||
},
|
||||
events::{setup_restarting_mgr_std, EventManager},
|
||||
executors::{
|
||||
inprocess::InProcessExecutor, timeout::TimeoutExecutor, Executor, ExitKind, HasObservers,
|
||||
},
|
||||
feedbacks::{CrashFeedback, MaxMapFeedback, TimeoutFeedback},
|
||||
fuzzer::{Fuzzer, StdFuzzer},
|
||||
inputs::{HasTargetBytes, Input},
|
||||
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
|
||||
mutators::token_mutations::Tokens,
|
||||
observers::{HitcountsMapObserver, ObserversTuple, StdMapObserver},
|
||||
stages::mutational::StdMutationalStage,
|
||||
state::{HasCorpus, HasMetadata, State},
|
||||
stats::SimpleStats,
|
||||
utils::{current_nanos, StdRand},
|
||||
Error,
|
||||
};
|
||||
|
||||
use frida_gum::{
|
||||
stalker::{NoneEventSink, Stalker},
|
||||
Gum, NativePointer,
|
||||
};
|
||||
|
||||
use std::{env, ffi::c_void, marker::PhantomData, path::PathBuf, time::Duration};
|
||||
|
||||
use libafl_frida::{
|
||||
asan_rt::{AsanErrorsFeedback, AsanErrorsObserver, ASAN_ERRORS},
|
||||
helper::{FridaHelper, FridaInstrumentationHelper, MAP_SIZE},
|
||||
FridaOptions,
|
||||
};
|
||||
|
||||
struct FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT>
|
||||
where
|
||||
FH: FridaHelper<'b>,
|
||||
H: FnMut(&[u8]) -> ExitKind,
|
||||
I: Input + HasTargetBytes,
|
||||
OT: ObserversTuple,
|
||||
{
|
||||
base: TimeoutExecutor<InProcessExecutor<'a, H, I, OT>, I, OT>,
|
||||
/// Frida's dynamic rewriting engine
|
||||
stalker: Stalker<'a>,
|
||||
/// User provided callback for instrumentation
|
||||
helper: &'c mut FH,
|
||||
followed: bool,
|
||||
_phantom: PhantomData<&'b u8>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, FH, H, I, OT> Executor<I> for FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT>
|
||||
where
|
||||
FH: FridaHelper<'b>,
|
||||
H: FnMut(&[u8]) -> ExitKind,
|
||||
I: Input + HasTargetBytes,
|
||||
OT: ObserversTuple,
|
||||
{
|
||||
/// Called right before exexution starts
|
||||
#[inline]
|
||||
fn pre_exec<EM, S>(&mut self, state: &mut S, event_mgr: &mut EM, input: &I) -> Result<(), Error>
|
||||
where
|
||||
EM: EventManager<I, S>,
|
||||
{
|
||||
if self.helper.stalker_enabled() {
|
||||
if !self.followed {
|
||||
self.followed = true;
|
||||
self.stalker
|
||||
.follow_me::<NoneEventSink>(self.helper.transformer(), None);
|
||||
} else {
|
||||
self.stalker.activate(NativePointer(
|
||||
self.base.inner().harness_mut() as *mut _ as *mut c_void
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
self.helper.pre_exec(input);
|
||||
|
||||
self.base.pre_exec(state, event_mgr, input)
|
||||
}
|
||||
|
||||
/// Instruct the target about the input and run
|
||||
#[inline]
|
||||
fn run_target(&mut self, input: &I) -> Result<ExitKind, Error> {
|
||||
let res = self.base.run_target(input);
|
||||
if unsafe { ASAN_ERRORS.is_some() && !ASAN_ERRORS.as_ref().unwrap().is_empty() } {
|
||||
println!("Crashing target as it had ASAN errors");
|
||||
unsafe {
|
||||
libc::raise(libc::SIGABRT);
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
/// Called right after execution finished.
|
||||
#[inline]
|
||||
fn post_exec<EM, S>(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
event_mgr: &mut EM,
|
||||
input: &I,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
EM: EventManager<I, S>,
|
||||
{
|
||||
if self.helper.stalker_enabled() {
|
||||
self.stalker.deactivate();
|
||||
}
|
||||
self.helper.post_exec(input);
|
||||
self.base.post_exec(state, event_mgr, input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, FH, H, I, OT> HasObservers<OT> for FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT>
|
||||
where
|
||||
FH: FridaHelper<'b>,
|
||||
H: FnMut(&[u8]) -> ExitKind,
|
||||
I: Input + HasTargetBytes,
|
||||
OT: ObserversTuple,
|
||||
{
|
||||
#[inline]
|
||||
fn observers(&self) -> &OT {
|
||||
self.base.observers()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn observers_mut(&mut self) -> &mut OT {
|
||||
self.base.observers_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, FH, H, I, OT> Named for FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT>
|
||||
where
|
||||
FH: FridaHelper<'b>,
|
||||
H: FnMut(&[u8]) -> ExitKind,
|
||||
I: Input + HasTargetBytes,
|
||||
OT: ObserversTuple,
|
||||
{
|
||||
fn name(&self) -> &str {
|
||||
self.base.name()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, FH, H, I, OT> FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT>
|
||||
where
|
||||
FH: FridaHelper<'b>,
|
||||
H: FnMut(&[u8]) -> ExitKind,
|
||||
I: Input + HasTargetBytes,
|
||||
OT: ObserversTuple,
|
||||
{
|
||||
pub fn new(
|
||||
gum: &'a Gum,
|
||||
base: InProcessExecutor<'a, H, I, OT>,
|
||||
helper: &'c mut FH,
|
||||
timeout: Duration,
|
||||
) -> Self {
|
||||
let stalker = Stalker::new(gum);
|
||||
|
||||
// Let's exclude the main module and libc.so at least:
|
||||
//stalker.exclude(&MemoryRange::new(
|
||||
//Module::find_base_address(&env::args().next().unwrap()),
|
||||
//get_module_size(&env::args().next().unwrap()),
|
||||
//));
|
||||
//stalker.exclude(&MemoryRange::new(
|
||||
//Module::find_base_address("libc.so"),
|
||||
//get_module_size("libc.so"),
|
||||
//));
|
||||
|
||||
Self {
|
||||
base: TimeoutExecutor::new(base, timeout),
|
||||
stalker,
|
||||
helper,
|
||||
followed: false,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The main fn, usually parsing parameters, and starting the fuzzer
|
||||
pub fn main() {
|
||||
// Registry the metadata types used in this fuzzer
|
||||
// Needed only on no_std
|
||||
//RegistryBuilder::register::<Tokens>();
|
||||
|
||||
println!(
|
||||
"Workdir: {:?}",
|
||||
env::current_dir().unwrap().to_string_lossy().to_string()
|
||||
);
|
||||
unsafe {
|
||||
fuzz(
|
||||
&env::args().nth(1).expect("no module specified"),
|
||||
&env::args().nth(2).expect("no symbol specified"),
|
||||
env::args()
|
||||
.nth(3)
|
||||
.expect("no modules to instrument specified")
|
||||
.split(":")
|
||||
.collect(),
|
||||
vec![PathBuf::from("./corpus")],
|
||||
PathBuf::from("./crashes"),
|
||||
1337,
|
||||
)
|
||||
.expect("An error occurred while fuzzing");
|
||||
}
|
||||
}
|
||||
|
||||
/// Not supported on windows right now
|
||||
#[cfg(windows)]
|
||||
fn fuzz(
|
||||
_module_name: &str,
|
||||
_symbol_name: &str,
|
||||
_corpus_dirs: Vec<PathBuf>,
|
||||
_objective_dir: PathBuf,
|
||||
_broker_port: u16,
|
||||
) -> Result<(), ()> {
|
||||
todo!("Example not supported on Windows");
|
||||
}
|
||||
|
||||
/// The actual fuzzer
|
||||
#[cfg(unix)]
|
||||
unsafe fn fuzz(
|
||||
module_name: &str,
|
||||
symbol_name: &str,
|
||||
modules_to_instrument: Vec<&str>,
|
||||
corpus_dirs: Vec<PathBuf>,
|
||||
objective_dir: PathBuf,
|
||||
broker_port: u16,
|
||||
) -> Result<(), Error> {
|
||||
// 'While the stats are state, they are usually used in the broker - which is likely never restarted
|
||||
let stats = SimpleStats::new(|s| println!("{}", s));
|
||||
|
||||
// The restarting state will spawn the same process again as child, then restarted it each time it crashes.
|
||||
let (state, mut restarting_mgr) = match setup_restarting_mgr_std(stats, broker_port) {
|
||||
Ok(res) => res,
|
||||
Err(err) => match err {
|
||||
Error::ShuttingDown => {
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
panic!("Failed to setup the restarter: {}", err);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let gum = Gum::obtain();
|
||||
|
||||
let lib = libloading::Library::new(module_name).unwrap();
|
||||
let target_func: libloading::Symbol<unsafe extern "C" fn(data: *const u8, size: usize) -> i32> =
|
||||
lib.get(symbol_name.as_bytes()).unwrap();
|
||||
|
||||
let mut frida_harness = move |buf: &[u8]| {
|
||||
(target_func)(buf.as_ptr(), buf.len());
|
||||
ExitKind::Ok
|
||||
};
|
||||
|
||||
let mut frida_helper = FridaInstrumentationHelper::new(
|
||||
&gum,
|
||||
FridaOptions::parse_env_options(),
|
||||
module_name,
|
||||
&modules_to_instrument,
|
||||
);
|
||||
|
||||
// Create an observation channel using the coverage map
|
||||
let edges_observer = HitcountsMapObserver::new(StdMapObserver::new_from_ptr(
|
||||
"edges",
|
||||
frida_helper.map_ptr(),
|
||||
MAP_SIZE,
|
||||
));
|
||||
|
||||
// If not restarting, create a State from scratch
|
||||
let mut state = state.unwrap_or_else(|| {
|
||||
State::new(
|
||||
// RNG
|
||||
StdRand::with_seed(current_nanos()),
|
||||
// Corpus that will be evolved, we keep it in memory for performance
|
||||
InMemoryCorpus::new(),
|
||||
// Feedbacks to rate the interestingness of an input
|
||||
tuple_list!(MaxMapFeedback::new_with_observer_track(
|
||||
&edges_observer,
|
||||
true,
|
||||
false
|
||||
)),
|
||||
// Corpus in which we store solutions (crashes in this example),
|
||||
// on disk so the user can get them after stopping the fuzzer
|
||||
OnDiskCorpus::new_save_meta(objective_dir, Some(OnDiskMetadataFormat::JsonPretty))
|
||||
.unwrap(),
|
||||
// Feedbacks to recognize an input as solution
|
||||
tuple_list!(
|
||||
CrashFeedback::new(),
|
||||
TimeoutFeedback::new(),
|
||||
AsanErrorsFeedback::new()
|
||||
),
|
||||
)
|
||||
});
|
||||
|
||||
println!("We're a client, let's fuzz :)");
|
||||
|
||||
// Create a PNG dictionary if not existing
|
||||
if state.metadata().get::<Tokens>().is_none() {
|
||||
state.add_metadata(Tokens::new(vec![
|
||||
vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header
|
||||
b"IHDR".to_vec(),
|
||||
b"IDAT".to_vec(),
|
||||
b"PLTE".to_vec(),
|
||||
b"IEND".to_vec(),
|
||||
]));
|
||||
}
|
||||
|
||||
// Setup a basic mutator with a mutational stage
|
||||
let mutator = StdScheduledMutator::new(havoc_mutations());
|
||||
let stage = StdMutationalStage::new(mutator);
|
||||
|
||||
// A fuzzer with just one stage and a minimization+queue policy to get testcasess from the corpus
|
||||
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
|
||||
let mut fuzzer = StdFuzzer::new(tuple_list!(stage));
|
||||
|
||||
frida_helper.register_thread();
|
||||
|
||||
// Create the executor for an in-process function with just one observer for edge coverage
|
||||
let mut executor = FridaInProcessExecutor::new(
|
||||
&gum,
|
||||
InProcessExecutor::new(
|
||||
"in-process(edges)",
|
||||
&mut frida_harness,
|
||||
tuple_list!(edges_observer, AsanErrorsObserver::new(&ASAN_ERRORS)),
|
||||
&mut state,
|
||||
&mut restarting_mgr,
|
||||
)?,
|
||||
&mut frida_helper,
|
||||
Duration::new(10, 0),
|
||||
);
|
||||
// Let's exclude the main module and libc.so at least:
|
||||
//executor.stalker.exclude(&MemoryRange::new(
|
||||
//Module::find_base_address(&env::args().next().unwrap()),
|
||||
//get_module_size(&env::args().next().unwrap()),
|
||||
//));
|
||||
//executor.stalker.exclude(&MemoryRange::new(
|
||||
//Module::find_base_address("libc.so"),
|
||||
//get_module_size("libc.so"),
|
||||
//));
|
||||
|
||||
// In case the corpus is empty (on first run), reset
|
||||
if state.corpus().count() < 1 {
|
||||
state
|
||||
.load_initial_inputs(&mut executor, &mut restarting_mgr, &scheduler, &corpus_dirs)
|
||||
.expect(&format!(
|
||||
"Failed to load initial corpus at {:?}",
|
||||
&corpus_dirs
|
||||
));
|
||||
println!("We imported {} inputs from disk.", state.corpus().count());
|
||||
}
|
||||
|
||||
fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr, &scheduler)?;
|
||||
|
||||
// Never reached
|
||||
Ok(())
|
||||
}
|
10
fuzzers/frida_libpng/src/main.rs
Normal file
@ -0,0 +1,10 @@
|
||||
#[cfg(unix)]
|
||||
mod fuzzer;
|
||||
#[cfg(unix)]
|
||||
pub fn main() {
|
||||
fuzzer::main();
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
pub fn main() {
|
||||
todo!("Frida not yet supported on this OS.");
|
||||
}
|
3
fuzzers/libfuzzer_libmozjpeg/.gitignore
vendored
@ -1 +1,2 @@
|
||||
*.tar.gz
|
||||
*.tar.gz*
|
||||
mozjpeg-4.0.3
|
@ -1,31 +1,29 @@
|
||||
[package]
|
||||
name = "libfuzzer_libmozjpeg"
|
||||
version = "0.1.0"
|
||||
authors = ["Marcin Kozlowski <marcinguy@gmail.com>"]
|
||||
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
|
||||
#[profile.release]
|
||||
#lto = true
|
||||
#codegen-units = 1
|
||||
#opt-level = 3
|
||||
#debug = true
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[dependencies]
|
||||
libafl = { path = "../../libafl/" }
|
||||
libafl_targets = { path = "../../libafl_targets/", features = ["pcguard_edges", "value_profile", "libfuzzer"] }
|
||||
# TODO Include it only when building cc
|
||||
libafl_cc = { path = "../../libafl_cc/" }
|
||||
|
||||
[build-dependencies]
|
||||
cc = { version = "1.0", features = ["parallel"] }
|
||||
num_cpus = "1.0"
|
||||
|
||||
[dependencies]
|
||||
libafl = { path = "../../libafl/" }
|
||||
|
||||
[[example]]
|
||||
[lib]
|
||||
name = "libfuzzer_libmozjpeg"
|
||||
path = "./src/fuzzer.rs"
|
||||
test = false
|
||||
bench = false
|
||||
crate-type = ["staticlib"]
|
||||
|
@ -1,14 +1,39 @@
|
||||
# Libfuzzer for libmozjpeg
|
||||
|
||||
This folder contains an example fuzzer for libmozjpeg, using LLMP for fast multi-process fuzzing and crash detection.
|
||||
Alongside the traditional edge coverage, this example shows how to use a value-profile like feedback to bypass CMPs and an allocations size maximization feedback to spot patological inputs in terms of memory usage.
|
||||
It has been tested on Linux.
|
||||
|
||||
## Build
|
||||
|
||||
To build this example, run `cargo build --example libfuzzer_libmozjpeg --release`.
|
||||
This will call (the build.rs)[./builld.rs], which in turn downloads a libmozjpeg archive from the web.
|
||||
Then, it will link (the fuzzer)[./src/fuzzer.rs] against (the c++ harness)[./harness.cc] and the instrumented `libmozjpeg`.
|
||||
Afterwards, the fuzzer will be ready to run, from `../../target/examples/libfuzzer_libmozjpeg`.
|
||||
To build this example, run `cargo build --release`.
|
||||
This will build the library with the fuzzer (src/lib.rs) with the libfuzzer compatibility layer. the SanitizerCoverage runtime functions for edges and value-profile feedbacks and the `hook_allocs.c` C file that hooks the allocator to report the size to the fuzzer.
|
||||
In addition, it will build also two C and C++ compiler wrappers (bin/c(c/xx).rs) that you must use to compile the target.
|
||||
|
||||
Then download the mozjpeg source tarball from and unpack the archive:
|
||||
```bash
|
||||
wget https://github.com/mozilla/mozjpeg/archive/v4.0.3.tar.gz
|
||||
tar -xzvf v4.0.3.tar.gz
|
||||
```
|
||||
|
||||
Now compile it with:
|
||||
|
||||
```
|
||||
cd mozjpeg-4.0.3
|
||||
cmake --disable-shared . -DCMAKE_C_COMPILER=$(realpath ../target/release/libafl_cc) -DCMAKE_CXX_COMPILER=$(realpath ../target/release/libafl_cxx) -G "Unix Makefiles"
|
||||
make -j `nproc`
|
||||
cd ..
|
||||
```
|
||||
|
||||
Now, we have to build the libfuzzer harness and link all together to create our fuzzer binary.
|
||||
|
||||
```
|
||||
./target/debug/cxx ./harness.cc ./mozjpeg-4.0.3/*.a -I ./mozjpeg-4.0.3/ -o fuzzer_mozjpeg
|
||||
```
|
||||
|
||||
Afterward, the fuzzer will be ready to run by simply executing `./fuzzer_mozjpeg`.
|
||||
Note that, unless you use the `launcher`, you will have to run the binary multiple times to actually start the fuzz process, see `Run` in the following.
|
||||
This allows you to run multiple different builds of the same fuzzer alongside, for example, with and without ASAN (`-fsanitize=address`) or with different mutators.
|
||||
|
||||
## Run
|
||||
|
||||
@ -19,10 +44,4 @@ As this example uses in-process fuzzing, we added a Restarting Event Manager (`s
|
||||
This means each client will start itself again to listen for crashes and timeouts.
|
||||
By restarting the actual fuzzer, it can recover from these exit conditions.
|
||||
|
||||
For convenience, you may just run `./test.sh` in this folder or:
|
||||
|
||||
broker.sh - starts the broker
|
||||
start.sh - starts as many clients as there are cores
|
||||
stop.sh - stop everything
|
||||
|
||||
|
||||
In any real-world scenario, you should use `taskset` to pin each client to an empty CPU core, the lib does not pick an empty core automatically, unless you use the `launcher`.
|
||||
|
@ -1,3 +0,0 @@
|
||||
#!/bin/bash
|
||||
taskset -c 0 ./.libfuzzer_test.elf
|
||||
|
@ -1,119 +1,21 @@
|
||||
// build.rs
|
||||
|
||||
use std::{
|
||||
env,
|
||||
path::Path,
|
||||
process::{exit, Command},
|
||||
};
|
||||
|
||||
const LIBMOZJPEG_URL: &str = "https://github.com/mozilla/mozjpeg/archive/v4.0.3.tar.gz";
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
if cfg!(windows) {
|
||||
println!("cargo:warning=Skipping libmozjpeg example on Windows");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let cwd = env::current_dir().unwrap().to_string_lossy().to_string();
|
||||
let out_dir = out_dir.to_string_lossy().to_string();
|
||||
let out_dir_path = Path::new(&out_dir);
|
||||
|
||||
println!("cargo:rerun-if-changed=./runtime/rt.c",);
|
||||
println!("cargo:rerun-if-changed=harness.cc");
|
||||
|
||||
let libmozjpeg = format!("{}/mozjpeg-4.0.3", &out_dir);
|
||||
let libmozjpeg_path = Path::new(&libmozjpeg);
|
||||
let libmozjpeg_tar = format!("{}/v4.0.3.tar.gz", &cwd);
|
||||
|
||||
// Enforce clang for its -fsanitize-coverage support.
|
||||
std::env::set_var("CC", "clang");
|
||||
std::env::set_var("CXX", "clang++");
|
||||
|
||||
if !libmozjpeg_path.is_dir() {
|
||||
if !Path::new(&libmozjpeg_tar).is_file() {
|
||||
println!("cargo:warning=Libmozjpeg not found, downloading...");
|
||||
// Download libmozjpeg
|
||||
Command::new("wget")
|
||||
.arg("-c")
|
||||
.arg(LIBMOZJPEG_URL)
|
||||
.arg("-O")
|
||||
.arg(&libmozjpeg_tar)
|
||||
.status()
|
||||
.unwrap();
|
||||
}
|
||||
Command::new("tar")
|
||||
.current_dir(&out_dir_path)
|
||||
.arg("-xvf")
|
||||
.arg(&libmozjpeg_tar)
|
||||
.status()
|
||||
.unwrap();
|
||||
Command::new(format!("{}/cmake", &libmozjpeg))
|
||||
.current_dir(&out_dir_path)
|
||||
.args(&[
|
||||
"-G\"Unix Makefiles\"",
|
||||
"--disable-shared",
|
||||
&libmozjpeg,
|
||||
"CC=clang",
|
||||
"CFLAGS=-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||
"LDFLAGS=-g -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||
])
|
||||
.env("CC", "clang")
|
||||
.env("CXX", "clang++")
|
||||
.env(
|
||||
"CFLAGS",
|
||||
"-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||
)
|
||||
.env(
|
||||
"CXXFLAGS",
|
||||
"-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||
)
|
||||
.env("LDFLAGS", "-g -fPIE -fsanitize-coverage=trace-pc-guard");
|
||||
Command::new("make")
|
||||
.current_dir(&libmozjpeg_path)
|
||||
//.arg(&format!("-j{}", num_cpus::get()))
|
||||
.args(&[
|
||||
"CC=clang",
|
||||
"CXX=clang++",
|
||||
"CFLAGS=-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||
"LDFLAGS=-g -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||
"CXXFLAGS=-D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||
])
|
||||
.env("CC", "clang")
|
||||
.env("CXX", "clang++")
|
||||
.env(
|
||||
"CFLAGS",
|
||||
"-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||
)
|
||||
.env(
|
||||
"CXXFLAGS",
|
||||
"-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||
)
|
||||
.env("LDFLAGS", "-g -fPIE -fsanitize-coverage=trace-pc-guard")
|
||||
.status()
|
||||
.unwrap();
|
||||
}
|
||||
println!("cargo:rerun-if-changed=harness.c");
|
||||
|
||||
cc::Build::new()
|
||||
.file("../libfuzzer_runtime/rt.c")
|
||||
.compile("libfuzzer-sys");
|
||||
|
||||
cc::Build::new()
|
||||
.include(&libmozjpeg_path)
|
||||
.flag("-fsanitize-coverage=trace-pc-guard")
|
||||
.file("./harness.cc")
|
||||
.compile("libfuzzer-harness");
|
||||
// Use sanitizer coverage to track the edges in the PUT
|
||||
// Take advantage of LTO (needs lld-link set in your cargo config)
|
||||
//.flag("-flto=thin")
|
||||
.file("./hook_allocs.c")
|
||||
.compile("hook_allocs");
|
||||
|
||||
println!("cargo:rustc-link-search=native={}", &out_dir);
|
||||
println!("cargo:rustc-link-search=native={}/", &libmozjpeg);
|
||||
println!("cargo:rustc-link-lib=static=jpeg");
|
||||
|
||||
//Deps for libmozjpeg: -pthread -lz -lm
|
||||
println!("cargo:rustc-link-lib=dylib=m");
|
||||
println!("cargo:rustc-link-lib=dylib=z");
|
||||
|
||||
//For the C++ harness
|
||||
println!("cargo:rustc-link-lib=static=stdc++");
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
}
|
||||
|
62
fuzzers/libfuzzer_libmozjpeg/hook_allocs.c
Normal file
@ -0,0 +1,62 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MAP_SIZE (16*1024)
|
||||
|
||||
#ifdef _WIN32
|
||||
#define posix_memalign(p, a, s) (((*(p)) = _aligned_malloc((s), (a))), *(p) ?0 :errno)
|
||||
#define RETADDR (uintptr_t)_ReturnAddress()
|
||||
#else
|
||||
#define RETADDR (uintptr_t)__builtin_return_address(0)
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define MAX(a, b) \
|
||||
({ \
|
||||
\
|
||||
__typeof__(a) _a = (a); \
|
||||
__typeof__(b) _b = (b); \
|
||||
_a > _b ? _a : _b; \
|
||||
\
|
||||
})
|
||||
#else
|
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
size_t libafl_alloc_map[MAP_SIZE];
|
||||
|
||||
void *malloc(size_t size) {
|
||||
|
||||
uintptr_t k = RETADDR;
|
||||
k = (k >> 4) ^ (k << 8);
|
||||
k &= MAP_SIZE - 1;
|
||||
libafl_alloc_map[k] = MAX(libafl_alloc_map[k], size);
|
||||
|
||||
// We cannot malloc in malloc.
|
||||
// Hence, even realloc(NULL, size) would loop in an optimized build.
|
||||
// We fall back to a stricter allocation function. Fingers crossed.
|
||||
void *ret = NULL;
|
||||
if (posix_memalign(&ret, 1<<6, size) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
void *calloc(size_t nmemb, size_t size) {
|
||||
|
||||
size *= nmemb;
|
||||
|
||||
uintptr_t k = RETADDR;
|
||||
k = (k >> 4) ^ (k << 8);
|
||||
k &= MAP_SIZE - 1;
|
||||
libafl_alloc_map[k] = MAX(libafl_alloc_map[k], size);
|
||||
|
||||
void *ret = NULL;
|
||||
if (posix_memalign(&ret, 1<<6, size) != 0) {
|
||||
return NULL;
|
||||
};
|
||||
return ret;
|
||||
|
||||
}
|
33
fuzzers/libfuzzer_libmozjpeg/src/bin/libafl_cc.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use libafl_cc::{ClangWrapper, CompilerWrapper, LIB_EXT, LIB_PREFIX};
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() > 1 {
|
||||
let mut dir = env::current_exe().unwrap();
|
||||
dir.pop();
|
||||
|
||||
let mut cc = ClangWrapper::new("clang", "clang++");
|
||||
cc.from_args(&args)
|
||||
.unwrap()
|
||||
.add_arg("-fsanitize-coverage=trace-pc-guard,trace-cmp".into())
|
||||
.unwrap()
|
||||
.add_arg("-fPIC".into())
|
||||
.unwrap()
|
||||
.add_link_arg(
|
||||
dir.join(format!("{}libfuzzer_libmozjpeg.{}", LIB_PREFIX, LIB_EXT))
|
||||
.display()
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
// Libraries needed by libafl on Windows
|
||||
#[cfg(windows)]
|
||||
cc.add_link_arg("-lws2_32".into())
|
||||
.unwrap()
|
||||
.add_link_arg("-lBcrypt".into())
|
||||
.unwrap()
|
||||
.add_link_arg("-lAdvapi32".into())
|
||||
.unwrap();
|
||||
cc.run().unwrap();
|
||||
}
|
||||
}
|
34
fuzzers/libfuzzer_libmozjpeg/src/bin/libafl_cxx.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use libafl_cc::{ClangWrapper, CompilerWrapper, LIB_EXT, LIB_PREFIX};
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() > 1 {
|
||||
let mut dir = env::current_exe().unwrap();
|
||||
dir.pop();
|
||||
|
||||
let mut cc = ClangWrapper::new("clang", "clang++");
|
||||
cc.is_cpp()
|
||||
.from_args(&args)
|
||||
.unwrap()
|
||||
.add_arg("-fsanitize-coverage=trace-pc-guard,trace-cmp".into())
|
||||
.unwrap()
|
||||
.add_arg("-fPIC".into())
|
||||
.unwrap()
|
||||
.add_link_arg(
|
||||
dir.join(format!("{}libfuzzer_libmozjpeg.{}", LIB_PREFIX, LIB_EXT))
|
||||
.display()
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
// Libraries needed by libafl on Windows
|
||||
#[cfg(windows)]
|
||||
cc.add_link_arg("-lws2_32".into())
|
||||
.unwrap()
|
||||
.add_link_arg("-lBcrypt".into())
|
||||
.unwrap()
|
||||
.add_link_arg("-lAdvapi32".into())
|
||||
.unwrap();
|
||||
cc.run().unwrap();
|
||||
}
|
||||
}
|
@ -3,16 +3,14 @@
|
||||
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
#[cfg(unix)]
|
||||
use libafl::{
|
||||
bolts::{shmem::UnixShMem, tuples::tuple_list},
|
||||
bolts::tuples::tuple_list,
|
||||
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus, RandCorpusScheduler},
|
||||
events::setup_restarting_mgr,
|
||||
executors::{inprocess::InProcessExecutor, Executor, ExitKind},
|
||||
events::setup_restarting_mgr_std,
|
||||
executors::{inprocess::InProcessExecutor, ExitKind},
|
||||
feedbacks::{CrashFeedback, MaxMapFeedback},
|
||||
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer},
|
||||
inputs::Input,
|
||||
mutators::scheduled::HavocBytesMutator,
|
||||
fuzzer::{Fuzzer, StdFuzzer},
|
||||
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
|
||||
mutators::token_mutations::Tokens,
|
||||
observers::StdMapObserver,
|
||||
stages::mutational::StdMutationalStage,
|
||||
@ -22,35 +20,17 @@ use libafl::{
|
||||
Error,
|
||||
};
|
||||
|
||||
/// We will interact with a C++ target, so use external c functionality
|
||||
#[cfg(unix)]
|
||||
use libafl_targets::{
|
||||
libfuzzer_initialize, libfuzzer_test_one_input, CMP_MAP, CMP_MAP_SIZE, EDGES_MAP, MAX_EDGES_NUM,
|
||||
};
|
||||
|
||||
const ALLOC_MAP_SIZE: usize = 16 * 1024;
|
||||
extern "C" {
|
||||
/// int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
|
||||
fn LLVMFuzzerTestOneInput(data: *const u8, size: usize) -> i32;
|
||||
|
||||
// afl_libfuzzer_init calls LLVMFUzzerInitialize()
|
||||
fn afl_libfuzzer_init() -> i32;
|
||||
|
||||
static __lafl_edges_map: *mut u8;
|
||||
static __lafl_cmp_map: *mut u8;
|
||||
static __lafl_max_edges_size: u32;
|
||||
}
|
||||
|
||||
/// The wrapped harness function, calling out to the LLVM-style harness
|
||||
#[cfg(unix)]
|
||||
fn harness<E, I>(_executor: &E, buf: &[u8]) -> ExitKind
|
||||
where
|
||||
E: Executor<I>,
|
||||
I: Input,
|
||||
{
|
||||
// println!("{:?}", buf);
|
||||
unsafe {
|
||||
LLVMFuzzerTestOneInput(buf.as_ptr(), buf.len());
|
||||
}
|
||||
ExitKind::Ok
|
||||
static mut libafl_alloc_map: [usize; ALLOC_MAP_SIZE];
|
||||
}
|
||||
|
||||
/// The main fn, usually parsing parameters, and starting the fuzzer
|
||||
#[no_mangle]
|
||||
pub fn main() {
|
||||
// Registry the metadata types used in this fuzzer
|
||||
// Needed only on no_std
|
||||
@ -68,27 +48,25 @@ pub fn main() {
|
||||
.expect("An error occurred while fuzzing");
|
||||
}
|
||||
|
||||
/// Not supported on windows right now
|
||||
#[cfg(windows)]
|
||||
fn fuzz(_corpus_dirs: Vec<PathBuf>, _objective_dir: PathBuf, _broker_port: u16) -> Result<(), ()> {
|
||||
todo!("Example not supported on Windows");
|
||||
}
|
||||
|
||||
/// The actual fuzzer
|
||||
#[cfg(unix)]
|
||||
fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> {
|
||||
// 'While the stats are state, they are usually used in the broker - which is likely never restarted
|
||||
let stats = SimpleStats::new(|s| println!("{}", s));
|
||||
|
||||
// The restarting state will spawn the same process again as child, then restarted it each time it crashes.
|
||||
let (state, mut restarting_mgr) =
|
||||
setup_restarting_mgr::<_, _, UnixShMem, _>(stats, broker_port)
|
||||
.expect("Failed to setup the restarter".into());
|
||||
setup_restarting_mgr_std(stats, broker_port).expect("Failed to setup the restarter".into());
|
||||
|
||||
// Create an observation channel using the coverage map
|
||||
let edges_observer = unsafe {
|
||||
StdMapObserver::new_from_ptr("edges", __lafl_edges_map, __lafl_max_edges_size as usize)
|
||||
};
|
||||
let edges_observer =
|
||||
StdMapObserver::new("edges", unsafe { &mut EDGES_MAP }, unsafe { MAX_EDGES_NUM });
|
||||
|
||||
// Create an observation channel using the cmp map
|
||||
let cmps_observer = StdMapObserver::new("cmps", unsafe { &mut CMP_MAP }, CMP_MAP_SIZE);
|
||||
|
||||
// Create an observation channel using the allocations map
|
||||
let allocs_observer =
|
||||
StdMapObserver::new("allocs", unsafe { &mut libafl_alloc_map }, ALLOC_MAP_SIZE);
|
||||
|
||||
// If not restarting, create a State from scratch
|
||||
let mut state = state.unwrap_or_else(|| {
|
||||
@ -98,7 +76,11 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
|
||||
// Corpus that will be evolved, we keep it in memory for performance
|
||||
InMemoryCorpus::new(),
|
||||
// Feedbacks to rate the interestingness of an input
|
||||
tuple_list!(MaxMapFeedback::new_with_observer(&edges_observer)),
|
||||
tuple_list!(
|
||||
MaxMapFeedback::new_with_observer(&edges_observer),
|
||||
MaxMapFeedback::new_with_observer(&cmps_observer),
|
||||
MaxMapFeedback::new_with_observer(&allocs_observer)
|
||||
),
|
||||
// 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(),
|
||||
@ -111,42 +93,44 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
|
||||
|
||||
// Add the JPEG tokens if not existing
|
||||
if state.metadata().get::<Tokens>().is_none() {
|
||||
state.add_metadata(Tokens::from_tokens_file("./jpeg.tkns")?);
|
||||
state.add_metadata(Tokens::from_tokens_file("./jpeg.dict")?);
|
||||
}
|
||||
|
||||
// Setup a basic mutator with a mutational stage
|
||||
let mutator = HavocBytesMutator::default();
|
||||
let mutator = StdScheduledMutator::new(havoc_mutations());
|
||||
let stage = StdMutationalStage::new(mutator);
|
||||
|
||||
// A fuzzer with just one stage and a random policy to get testcasess from the corpus
|
||||
let fuzzer = StdFuzzer::new(RandCorpusScheduler::new(), tuple_list!(stage));
|
||||
// A random policy to get testcasess from the corpus
|
||||
let scheduler = RandCorpusScheduler::new();
|
||||
// A fuzzer with just one stage
|
||||
let mut fuzzer = StdFuzzer::new(tuple_list!(stage));
|
||||
|
||||
// Create the executor for an in-process function with just one observer for edge coverage
|
||||
// The wrapped harness function, calling out to the LLVM-style harness
|
||||
let mut harness = |buf: &[u8]| {
|
||||
libfuzzer_test_one_input(buf);
|
||||
ExitKind::Ok
|
||||
};
|
||||
|
||||
// Create the executor for an in-process function with observers for edge coverage, value-profile and allocations sizes
|
||||
let mut executor = InProcessExecutor::new(
|
||||
"in-process(edges)",
|
||||
harness,
|
||||
tuple_list!(edges_observer),
|
||||
"in-process(edges,cmp,alloc)",
|
||||
&mut harness,
|
||||
tuple_list!(edges_observer, cmps_observer, allocs_observer),
|
||||
&mut state,
|
||||
&mut restarting_mgr,
|
||||
)?;
|
||||
|
||||
// The actual target run starts here.
|
||||
// Call LLVMFUzzerInitialize() if present.
|
||||
unsafe {
|
||||
if afl_libfuzzer_init() == -1 {
|
||||
println!("Warning: LLVMFuzzerInitialize failed with -1")
|
||||
}
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if libfuzzer_initialize(&args) == -1 {
|
||||
println!("Warning: LLVMFuzzerInitialize failed with -1")
|
||||
}
|
||||
|
||||
// In case the corpus is empty (on first run), reset
|
||||
if state.corpus().count() < 1 {
|
||||
state
|
||||
.load_initial_inputs(
|
||||
&mut executor,
|
||||
&mut restarting_mgr,
|
||||
fuzzer.scheduler(),
|
||||
&corpus_dirs,
|
||||
)
|
||||
.load_initial_inputs(&mut executor, &mut restarting_mgr, &scheduler, &corpus_dirs)
|
||||
.expect(&format!(
|
||||
"Failed to load initial corpus at {:?}",
|
||||
&corpus_dirs
|
||||
@ -154,7 +138,7 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
|
||||
println!("We imported {} inputs from disk.", state.corpus().count());
|
||||
}
|
||||
|
||||
fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr)?;
|
||||
fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr, &scheduler)?;
|
||||
|
||||
// Never reached
|
||||
Ok(())
|
@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
cores=$(grep -c ^processor /proc/cpuinfo)
|
||||
for (( c=1;c<$cores;c++))
|
||||
do
|
||||
echo $c
|
||||
taskset -c $c ./.libfuzzer_test.elf 2>/dev/null &
|
||||
sleep 0.1
|
||||
done
|
||||
|
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
killall -9 .libfuzzer_test.elf
|
@ -1,17 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
mkdir -p ./crashes
|
||||
|
||||
cargo build --example libfuzzer_libmozjpeg --release || exit 1
|
||||
cp ../../target/release/examples/libfuzzer_libmozjpeg ./.libfuzzer_test.elf
|
||||
|
||||
# The broker
|
||||
RUST_BACKTRACE=full taskset -c 0 ./.libfuzzer_test.elf &
|
||||
# Give the broker time to spawn
|
||||
sleep 2
|
||||
echo "Spawning client"
|
||||
# The 1st fuzzer client, pin to cpu 0x1
|
||||
RUST_BACKTRACE=full taskset -c 1 ./.libfuzzer_test.elf 2>/dev/null
|
||||
|
||||
killall .libfuzzer_test.elf
|
||||
rm -rf ./.libfuzzer_test.elf
|
@ -3,9 +3,6 @@ name = "libfuzzer_libpng"
|
||||
version = "0.1.0"
|
||||
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
@ -19,13 +16,15 @@ std = []
|
||||
|
||||
[build-dependencies]
|
||||
cc = { version = "1.0", features = ["parallel"] }
|
||||
which = { version = "4.0.2" }
|
||||
num_cpus = "1.0"
|
||||
|
||||
[dependencies]
|
||||
libafl = { path = "../../libafl/" }
|
||||
libafl_targets = { path = "../../libafl_targets/", features = ["pcguard_hitcounts", "libfuzzer"] }
|
||||
# TODO Include it only when building cc
|
||||
libafl_cc = { path = "../../libafl_cc/" }
|
||||
|
||||
[[example]]
|
||||
[lib]
|
||||
name = "libfuzzer_libpng"
|
||||
path = "./src/fuzzer.rs"
|
||||
test = false
|
||||
bench = false
|
||||
crate-type = ["staticlib"]
|
||||
|
@ -1,25 +1,79 @@
|
||||
# Libfuzzer for libpng
|
||||
|
||||
This folder contains an example fuzzer for libpng, using LLMP for fast multi-process fuzzing and crash detection.
|
||||
To show off crash detection, we added a ud2 instruction to the harness, edit harness.cc if you want a non-crashing example.
|
||||
|
||||
In contrast to other fuzzer examples, this setup uses `fuzz_loop_for`, to occasionally respawn the fuzzer executor.
|
||||
While this costs performance, it can be useful for targets with memory leaks or other instabilities.
|
||||
If your target is really instable, however, consider exchanging the `InProcessExecutor` for a `ForkserverExecutor` instead.
|
||||
|
||||
To show off crash detection, we added a `ud2` instruction to the harness, edit harness.cc if you want a non-crashing example.
|
||||
It has been tested on Linux.
|
||||
|
||||
## Build
|
||||
|
||||
To build this example, run `cargo build --example libfuzzer_libpng --release`.
|
||||
This will call (the build.rs)[./builld.rs], which in turn downloads a libpng archive from the web.
|
||||
Then, it will link (the fuzzer)[./src/fuzzer.rs] against (the C++ harness)[./harness.cc] and the instrumented `libpng`.
|
||||
Afterwards, the fuzzer will be ready to run, from `../../target/examples/libfuzzer_libpng`.
|
||||
To build this example, run
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
This will build the library with the fuzzer (src/lib.rs) with the libfuzzer compatibility layer and the SanitizerCoverage runtime functions for coverage feedback.
|
||||
In addition, it will also build two C and C++ compiler wrappers (bin/libafl_c(libafl_c/xx).rs) that you must use to compile the target.
|
||||
|
||||
The compiler wrappers, `libafl_cc` and libafl_cxx`, will end up in `./target/release/` (or `./target/debug`, in case you did not build with the `--release` flag).
|
||||
|
||||
Then download libpng, and unpack the archive:
|
||||
```bash
|
||||
wget https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz
|
||||
tar -xvf libpng-1.6.37.tar.xz
|
||||
```
|
||||
|
||||
Now compile libpng, using the libafl_cc compiler wrapper:
|
||||
|
||||
```bash
|
||||
cd libpng-1.6.37
|
||||
./configure
|
||||
make CC=$(realpath ../target/release/libafl_cc) CXX=$(realpath ../target/release/libafl_cxx) -j `nproc`
|
||||
```
|
||||
|
||||
You can find the static lib at `libpng-1.6.37/.libs/libpng16.a`.
|
||||
|
||||
Now, we have to build the libfuzzer harness and link all together to create our fuzzer binary.
|
||||
|
||||
```
|
||||
cd ..
|
||||
./target/release/libafl_cxx ./harness.cc libpng-1.6.37/.libs/libpng16.a -I libpng-1.6.37/ -o fuzzer_libpng -lz -lm
|
||||
```
|
||||
|
||||
Afterward, the fuzzer will be ready to run.
|
||||
Note that, unless you use the `launcher`, you will have to run the binary multiple times to actually start the fuzz process, see `Run` in the following.
|
||||
This allows you to run multiple different builds of the same fuzzer alongside, for example, with and without ASAN (`-fsanitize=address`) or with different mutators.
|
||||
|
||||
## Run
|
||||
|
||||
The first time you run the binary, the broker will open a tcp port (currently on port `1337`), waiting for fuzzer clients to connect. This port is local and only used for the initial handshake. All further communication happens via shared map, to be independent of the kernel.
|
||||
The first time you run the binary, the broker will open a tcp port (currently on port `1337`), waiting for fuzzer clients to connect. This port is local and only used for the initial handshake. All further communication happens via shared map, to be independent of the kernel. Currently you must run the clients from the libfuzzer_libpng directory for them to be able to access the PNG corpus.
|
||||
|
||||
```
|
||||
./fuzzer_libpng
|
||||
|
||||
[libafl/src/bolts/llmp.rs:407] "We're the broker" = "We\'re the broker"
|
||||
Doing broker things. Run this tool again to start fuzzing in a client.
|
||||
```
|
||||
|
||||
And after running the above again in a separate terminal:
|
||||
|
||||
```
|
||||
[libafl/src/bolts/llmp.rs:1464] "New connection" = "New connection"
|
||||
[libafl/src/bolts/llmp.rs:1464] addr = 127.0.0.1:33500
|
||||
[libafl/src/bolts/llmp.rs:1464] stream.peer_addr().unwrap() = 127.0.0.1:33500
|
||||
[LOG Debug]: Loaded 4 initial testcases.
|
||||
[New Testcase #2] clients: 3, corpus: 6, objectives: 0, executions: 5, exec/sec: 0
|
||||
< fuzzing stats >
|
||||
```
|
||||
|
||||
Each following execution will run a fuzzer client.
|
||||
As this example uses in-process fuzzing, we added a Restarting Event Manager (`setup_restarting_mgr`).
|
||||
This means each client will start itself again to listen for crashes and timeouts.
|
||||
By restarting the actual fuzzer, it can recover from these exit conditions.
|
||||
|
||||
In any real-world scenario, you should use `taskset` to pin each client to an empty CPU core, the lib does not pick an empty core automatically (yet).
|
||||
|
||||
For convenience, you may just run `./test.sh` in this folder to test it.
|
||||
|
33
fuzzers/libfuzzer_libpng/src/bin/libafl_cc.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use libafl_cc::{ClangWrapper, CompilerWrapper, LIB_EXT, LIB_PREFIX};
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() > 1 {
|
||||
let mut dir = env::current_exe().unwrap();
|
||||
dir.pop();
|
||||
|
||||
let mut cc = ClangWrapper::new("clang", "clang++");
|
||||
cc.from_args(&args)
|
||||
.unwrap()
|
||||
.add_arg("-fsanitize-coverage=trace-pc-guard".into())
|
||||
.unwrap()
|
||||
.add_link_arg(
|
||||
dir.join(format!("{}libfuzzer_libpng.{}", LIB_PREFIX, LIB_EXT))
|
||||
.display()
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
// Libraries needed by libafl on Windows
|
||||
#[cfg(windows)]
|
||||
cc.add_link_arg("-lws2_32".into())
|
||||
.unwrap()
|
||||
.add_link_arg("-lBcrypt".into())
|
||||
.unwrap()
|
||||
.add_link_arg("-lAdvapi32".into())
|
||||
.unwrap();
|
||||
cc.run().unwrap();
|
||||
} else {
|
||||
panic!("LibAFL CC: No Arguments given");
|
||||
}
|
||||
}
|
34
fuzzers/libfuzzer_libpng/src/bin/libafl_cxx.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use libafl_cc::{ClangWrapper, CompilerWrapper, LIB_EXT, LIB_PREFIX};
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() > 1 {
|
||||
let mut dir = env::current_exe().unwrap();
|
||||
dir.pop();
|
||||
|
||||
let mut cc = ClangWrapper::new("clang", "clang++");
|
||||
cc.is_cpp()
|
||||
.from_args(&args)
|
||||
.unwrap()
|
||||
.add_arg("-fsanitize-coverage=trace-pc-guard".into())
|
||||
.unwrap()
|
||||
.add_link_arg(
|
||||
dir.join(format!("{}libfuzzer_libpng.{}", LIB_PREFIX, LIB_EXT))
|
||||
.display()
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
// Libraries needed by libafl on Windows
|
||||
#[cfg(windows)]
|
||||
cc.add_link_arg("-lws2_32".into())
|
||||
.unwrap()
|
||||
.add_link_arg("-lBcrypt".into())
|
||||
.unwrap()
|
||||
.add_link_arg("-lAdvapi32".into())
|
||||
.unwrap();
|
||||
cc.run().unwrap();
|
||||
} else {
|
||||
panic!("LibAFL CC: No Arguments given");
|
||||
}
|
||||
}
|
@ -4,19 +4,18 @@
|
||||
use core::time::Duration;
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
#[cfg(unix)]
|
||||
use libafl::{
|
||||
bolts::{shmem::UnixShMem, tuples::tuple_list},
|
||||
bolts::tuples::tuple_list,
|
||||
corpus::{
|
||||
Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus,
|
||||
QueueCorpusScheduler,
|
||||
},
|
||||
events::setup_restarting_mgr,
|
||||
executors::{inprocess::InProcessExecutor, Executor, ExitKind, TimeoutExecutor},
|
||||
events::{setup_restarting_mgr_std, EventManager},
|
||||
executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor},
|
||||
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
|
||||
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer},
|
||||
inputs::Input,
|
||||
mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens},
|
||||
fuzzer::{Fuzzer, StdFuzzer},
|
||||
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
|
||||
mutators::token_mutations::Tokens,
|
||||
observers::{HitcountsMapObserver, StdMapObserver, TimeObserver},
|
||||
stages::mutational::StdMutationalStage,
|
||||
state::{HasCorpus, HasMetadata, State},
|
||||
@ -25,35 +24,10 @@ use libafl::{
|
||||
Error,
|
||||
};
|
||||
|
||||
/// We will interact with a C++ target, so use external c functionality
|
||||
#[cfg(unix)]
|
||||
extern "C" {
|
||||
/// int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
|
||||
fn LLVMFuzzerTestOneInput(data: *const u8, size: usize) -> i32;
|
||||
use libafl_targets::{libfuzzer_initialize, libfuzzer_test_one_input, EDGES_MAP, MAX_EDGES_NUM};
|
||||
|
||||
// afl_libfuzzer_init calls LLVMFUzzerInitialize()
|
||||
fn afl_libfuzzer_init() -> i32;
|
||||
|
||||
static __lafl_edges_map: *mut u8;
|
||||
static __lafl_cmp_map: *mut u8;
|
||||
static __lafl_max_edges_size: u32;
|
||||
}
|
||||
|
||||
/// The wrapped harness function, calling out to the LLVM-style harness
|
||||
#[cfg(unix)]
|
||||
fn harness<E, I>(_executor: &E, buf: &[u8]) -> ExitKind
|
||||
where
|
||||
E: Executor<I>,
|
||||
I: Input,
|
||||
{
|
||||
// println!("{:?}", buf);
|
||||
unsafe {
|
||||
LLVMFuzzerTestOneInput(buf.as_ptr(), buf.len());
|
||||
}
|
||||
ExitKind::Ok
|
||||
}
|
||||
|
||||
/// The main fn, usually parsing parameters, and starting the fuzzer
|
||||
/// The main fn, no_mangle as it is a C main
|
||||
#[no_mangle]
|
||||
pub fn main() {
|
||||
// Registry the metadata types used in this fuzzer
|
||||
// Needed only on no_std
|
||||
@ -71,35 +45,27 @@ pub fn main() {
|
||||
.expect("An error occurred while fuzzing");
|
||||
}
|
||||
|
||||
/// Not supported on windows right now
|
||||
#[cfg(windows)]
|
||||
fn fuzz(_corpus_dirs: Vec<PathBuf>, _objective_dir: PathBuf, _broker_port: u16) -> Result<(), ()> {
|
||||
todo!("Example not supported on Windows");
|
||||
}
|
||||
|
||||
/// The actual fuzzer
|
||||
#[cfg(unix)]
|
||||
fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> {
|
||||
// 'While the stats are state, they are usually used in the broker - which is likely never restarted
|
||||
let stats = SimpleStats::new(|s| println!("{}", s));
|
||||
|
||||
// The restarting state will spawn the same process again as child, then restarted it each time it crashes.
|
||||
let (state, mut restarting_mgr) =
|
||||
match setup_restarting_mgr::<_, _, UnixShMem, _>(stats, broker_port) {
|
||||
Ok(res) => res,
|
||||
Err(err) => match err {
|
||||
Error::ShuttingDown => {
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
panic!("Failed to setup the restarter: {}", err);
|
||||
}
|
||||
},
|
||||
};
|
||||
let (state, mut restarting_mgr) = match setup_restarting_mgr_std(stats, broker_port) {
|
||||
Ok(res) => res,
|
||||
Err(err) => match err {
|
||||
Error::ShuttingDown => {
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
panic!("Failed to setup the restarter: {}", err);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Create an observation channel using the coverage map
|
||||
let edges_observer = HitcountsMapObserver::new(unsafe {
|
||||
StdMapObserver::new_from_ptr("edges", __lafl_edges_map, __lafl_max_edges_size as usize)
|
||||
StdMapObserver::new("edges", &mut EDGES_MAP, MAX_EDGES_NUM)
|
||||
});
|
||||
|
||||
// If not restarting, create a State from scratch
|
||||
@ -136,18 +102,26 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
|
||||
}
|
||||
|
||||
// Setup a basic mutator with a mutational stage
|
||||
let mutator = HavocBytesMutator::default();
|
||||
let mutator = StdScheduledMutator::new(havoc_mutations());
|
||||
let stage = StdMutationalStage::new(mutator);
|
||||
|
||||
// A fuzzer with just one stage and a minimization+queue policy to get testcasess from the corpus
|
||||
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
|
||||
let fuzzer = StdFuzzer::new(scheduler, tuple_list!(stage));
|
||||
// A fuzzer with just one stage
|
||||
let mut fuzzer = StdFuzzer::new(tuple_list!(stage));
|
||||
|
||||
// Create the executor for an in-process function with just one observer for edge coverage
|
||||
// A minimization+queue policy to get testcasess from the corpus
|
||||
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
|
||||
|
||||
// The wrapped harness function, calling out to the LLVM-style harness
|
||||
let mut harness = |buf: &[u8]| {
|
||||
libfuzzer_test_one_input(buf);
|
||||
ExitKind::Ok
|
||||
};
|
||||
|
||||
// Create the executor for an in-process function with one observer for edge coverage and one for the execution time
|
||||
let mut executor = TimeoutExecutor::new(
|
||||
InProcessExecutor::new(
|
||||
"in-process(edges)",
|
||||
harness,
|
||||
"in-process(edges,time)",
|
||||
&mut harness,
|
||||
tuple_list!(edges_observer, TimeObserver::new("time")),
|
||||
&mut state,
|
||||
&mut restarting_mgr,
|
||||
@ -158,21 +132,15 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
|
||||
|
||||
// The actual target run starts here.
|
||||
// Call LLVMFUzzerInitialize() if present.
|
||||
unsafe {
|
||||
if afl_libfuzzer_init() == -1 {
|
||||
println!("Warning: LLVMFuzzerInitialize failed with -1")
|
||||
}
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if libfuzzer_initialize(&args) == -1 {
|
||||
println!("Warning: LLVMFuzzerInitialize failed with -1")
|
||||
}
|
||||
|
||||
// In case the corpus is empty (on first run), reset
|
||||
if state.corpus().count() < 1 {
|
||||
state
|
||||
.load_initial_inputs(
|
||||
&mut executor,
|
||||
&mut restarting_mgr,
|
||||
fuzzer.scheduler(),
|
||||
&corpus_dirs,
|
||||
)
|
||||
.load_initial_inputs(&mut executor, &mut restarting_mgr, &scheduler, &corpus_dirs)
|
||||
.expect(&format!(
|
||||
"Failed to load initial corpus at {:?}",
|
||||
&corpus_dirs
|
||||
@ -180,8 +148,22 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
|
||||
println!("We imported {} inputs from disk.", state.corpus().count());
|
||||
}
|
||||
|
||||
fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr)?;
|
||||
// This fuzzer restarts after 1 mio `fuzz_one` executions.
|
||||
// Each fuzz_one will internally do many executions of the target.
|
||||
// If your target is very instable, setting a low count here may help.
|
||||
// However, you will lose a lot of performance that way.
|
||||
let iters = 1_000_000;
|
||||
fuzzer.fuzz_loop_for(
|
||||
&mut state,
|
||||
&mut executor,
|
||||
&mut restarting_mgr,
|
||||
&scheduler,
|
||||
iters,
|
||||
)?;
|
||||
|
||||
// It's important, that we store the state before restarting!
|
||||
// Else, the parent will not respawn a new child and quit.
|
||||
restarting_mgr.on_restart(&mut state)?;
|
||||
|
||||
// Never reached
|
||||
Ok(())
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
mkdir -p ./crashes
|
||||
rm -rf ./.libfuzzer_test.elf
|
||||
|
||||
cargo build --example libfuzzer_libpng --release || exit 1
|
||||
cp ../../target/release/examples/libfuzzer_libpng ./.libfuzzer_test.elf
|
||||
|
||||
# The broker
|
||||
RUST_BACKTRACE=full taskset -c 0 ./.libfuzzer_test.elf &
|
||||
# Give the broker time to spawn
|
||||
sleep 2
|
||||
echo "Spawning client"
|
||||
# The 1st fuzzer client, pin to cpu 0x1
|
||||
RUST_BACKTRACE=full taskset -c 1 ./.libfuzzer_test.elf 2>/dev/null
|
||||
|
||||
killall .libfuzzer_test.elf
|
||||
rm -rf ./.libfuzzer_test.elf
|
@ -1,109 +0,0 @@
|
||||
// build.rs
|
||||
|
||||
use std::{
|
||||
env,
|
||||
path::Path,
|
||||
process::{exit, Command},
|
||||
};
|
||||
|
||||
const LIBPNG_URL: &str =
|
||||
"https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz";
|
||||
|
||||
fn main() {
|
||||
if cfg!(windows) {
|
||||
println!("cargo:warning=Skipping libpng example on Windows");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let cwd = env::current_dir().unwrap().to_string_lossy().to_string();
|
||||
let out_dir = out_dir.to_string_lossy().to_string();
|
||||
let out_dir_path = Path::new(&out_dir);
|
||||
|
||||
println!("cargo:rerun-if-changed=../libfuzzer_runtime/rt.c",);
|
||||
println!("cargo:rerun-if-changed=harness.cc");
|
||||
|
||||
let libpng = format!("{}/libpng-1.6.37", &out_dir);
|
||||
let libpng_path = Path::new(&libpng);
|
||||
let libpng_tar = format!("{}/libpng-1.6.37.tar.xz", &cwd);
|
||||
|
||||
// Enforce clang for its -fsanitize-coverage support.
|
||||
std::env::set_var("CC", "clang");
|
||||
std::env::set_var("CXX", "clang++");
|
||||
let ldflags = match env::var("LDFLAGS") {
|
||||
Ok(val) => val,
|
||||
Err(_) => "".to_string(),
|
||||
};
|
||||
|
||||
if !libpng_path.is_dir() {
|
||||
if !Path::new(&libpng_tar).is_file() {
|
||||
println!("cargo:warning=Libpng not found, downloading...");
|
||||
// Download libpng
|
||||
Command::new("wget")
|
||||
.arg("-c")
|
||||
.arg(LIBPNG_URL)
|
||||
.arg("-O")
|
||||
.arg(&libpng_tar)
|
||||
.status()
|
||||
.unwrap();
|
||||
}
|
||||
Command::new("tar")
|
||||
.current_dir(&out_dir_path)
|
||||
.arg("-xvf")
|
||||
.arg(&libpng_tar)
|
||||
.status()
|
||||
.unwrap();
|
||||
Command::new(format!("{}/configure", &libpng))
|
||||
.current_dir(&libpng_path)
|
||||
.args(&[
|
||||
"--disable-shared",
|
||||
&format!("--host={}", env::var("TARGET").unwrap())[..],
|
||||
])
|
||||
.env("CC", "clang")
|
||||
.env("CXX", "clang++")
|
||||
.env(
|
||||
"CFLAGS",
|
||||
"-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||
)
|
||||
.env(
|
||||
"CXXFLAGS",
|
||||
"-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||
)
|
||||
.env(
|
||||
"LDFLAGS",
|
||||
format!("-g -fPIE -fsanitize-coverage=trace-pc-guard {}", ldflags),
|
||||
)
|
||||
.status()
|
||||
.unwrap();
|
||||
Command::new("make")
|
||||
.current_dir(&libpng_path)
|
||||
.status()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
cc::Build::new()
|
||||
.file("../libfuzzer_runtime/rt.c")
|
||||
.compile("libfuzzer-sys");
|
||||
|
||||
cc::Build::new()
|
||||
.include(&libpng_path)
|
||||
.cpp(true)
|
||||
.flag("-fsanitize-coverage=trace-pc-guard")
|
||||
// .define("HAS_DUMMY_CRASH", "1")
|
||||
.file("./harness.cc")
|
||||
.compile("libfuzzer-harness");
|
||||
|
||||
println!("cargo:rustc-link-search=native={}", &out_dir);
|
||||
println!("cargo:rustc-link-search=native={}/.libs", &libpng);
|
||||
println!("cargo:rustc-link-lib=static=png16");
|
||||
|
||||
//Deps for libpng: -pthread -lz -lm
|
||||
println!("cargo:rustc-link-lib=dylib=m");
|
||||
println!("cargo:rustc-link-lib=dylib=z");
|
||||
|
||||
//For the C++ harness
|
||||
//must by dylib for android
|
||||
println!("cargo:rustc-link-lib=dylib=stdc++");
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
mkdir -p ./crashes
|
||||
|
||||
cargo build --example libfuzzer_libpng --release || exit 1
|
||||
cp ../../target/release/examples/libfuzzer_libpng ./.libfuzzer_test.elf
|
||||
|
||||
# The broker
|
||||
RUST_BACKTRACE=full taskset 0 ./.libfuzzer_test.elf &
|
||||
# Give the broker time to spawn
|
||||
sleep 2
|
||||
echo "Spawning client"
|
||||
# The 1st fuzzer client, pin to cpu 0x1
|
||||
RUST_BACKTRACE=full taskset 1 ./.libfuzzer_test.elf 2>/dev/null
|
||||
|
||||
killall .libfuzzer_test.elf
|
||||
rm -rf ./.libfuzzer_test.elf
|
@ -1,185 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MAP_SIZE (16*1024)
|
||||
|
||||
int orig_argc;
|
||||
char **orig_argv;
|
||||
char **orig_envp;
|
||||
|
||||
uint8_t __lafl_dummy_map[MAP_SIZE];
|
||||
size_t __lafl_dummy_map_usize[MAP_SIZE];
|
||||
|
||||
uint8_t *__lafl_edges_map = __lafl_dummy_map;
|
||||
uint8_t *__lafl_cmp_map = __lafl_dummy_map;
|
||||
size_t *__lafl_alloc_map = __lafl_dummy_map_usize;
|
||||
|
||||
uint32_t __lafl_max_edges_size = 0;
|
||||
|
||||
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
|
||||
|
||||
uint32_t pos = *guard;
|
||||
uint16_t val = __lafl_edges_map[pos] + 1;
|
||||
__lafl_edges_map[pos] = ((uint8_t) val) + (uint8_t) (val >> 8);
|
||||
//__lafl_edges_map[pos] = 1;
|
||||
|
||||
}
|
||||
|
||||
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop) {
|
||||
|
||||
if (start == stop || *start) { return; }
|
||||
|
||||
*(start++) = (++__lafl_max_edges_size) & (MAP_SIZE -1);
|
||||
|
||||
while (start < stop) {
|
||||
|
||||
*start = (++__lafl_max_edges_size) & (MAP_SIZE -1);
|
||||
start++;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#define MAX(a, b) \
|
||||
({ \
|
||||
\
|
||||
__typeof__(a) _a = (a); \
|
||||
__typeof__(b) _b = (b); \
|
||||
_a > _b ? _a : _b; \
|
||||
\
|
||||
})
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#pragma weak __sanitizer_cov_trace_const_cmp1 = __sanitizer_cov_trace_cmp1
|
||||
#pragma weak __sanitizer_cov_trace_const_cmp2 = __sanitizer_cov_trace_cmp2
|
||||
#pragma weak __sanitizer_cov_trace_const_cmp4 = __sanitizer_cov_trace_cmp4
|
||||
#pragma weak __sanitizer_cov_trace_const_cmp8 = __sanitizer_cov_trace_cmp8
|
||||
#else
|
||||
void __sanitizer_cov_trace_const_cmp1(uint8_t arg1, uint8_t arg2) __attribute__((alias("__sanitizer_cov_trace_cmp1")));
|
||||
void __sanitizer_cov_trace_const_cmp2(uint16_t arg1, uint16_t arg2)
|
||||
__attribute__((alias("__sanitizer_cov_trace_cmp2")));
|
||||
void __sanitizer_cov_trace_const_cmp4(uint32_t arg1, uint32_t arg2)
|
||||
__attribute__((alias("__sanitizer_cov_trace_cmp4")));
|
||||
void __sanitizer_cov_trace_const_cmp8(uint64_t arg1, uint64_t arg2)
|
||||
__attribute__((alias("__sanitizer_cov_trace_cmp8")));
|
||||
#endif
|
||||
|
||||
void __sanitizer_cov_trace_cmp1(uint8_t arg1, uint8_t arg2) {
|
||||
|
||||
uintptr_t k = (uintptr_t)__builtin_return_address(0);
|
||||
k = (k >> 4) ^ (k << 8);
|
||||
k &= MAP_SIZE - 1;
|
||||
__lafl_cmp_map[k] = MAX(__lafl_cmp_map[k], (__builtin_popcount(~(arg1 ^ arg2))));
|
||||
|
||||
}
|
||||
|
||||
void __sanitizer_cov_trace_cmp2(uint16_t arg1, uint16_t arg2) {
|
||||
|
||||
uintptr_t k = (uintptr_t)__builtin_return_address(0);
|
||||
k = (k >> 4) ^ (k << 8);
|
||||
k &= MAP_SIZE - 1;
|
||||
__lafl_cmp_map[k] = MAX(__lafl_cmp_map[k], (__builtin_popcount(~(arg1 ^ arg2))));
|
||||
|
||||
}
|
||||
|
||||
void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2) {
|
||||
|
||||
uintptr_t k = (uintptr_t)__builtin_return_address(0);
|
||||
k = (k >> 4) ^ (k << 8);
|
||||
k &= MAP_SIZE - 1;
|
||||
__lafl_cmp_map[k] = MAX(__lafl_cmp_map[k], (__builtin_popcount(~(arg1 ^ arg2))));
|
||||
|
||||
}
|
||||
|
||||
void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2) {
|
||||
|
||||
uintptr_t k = (uintptr_t)__builtin_return_address(0);
|
||||
k = (k >> 4) ^ (k << 8);
|
||||
k &= MAP_SIZE - 1;
|
||||
__lafl_cmp_map[k] = MAX(__lafl_cmp_map[k], (__builtin_popcountll(~(arg1 ^ arg2))));
|
||||
|
||||
}
|
||||
|
||||
void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases) {
|
||||
|
||||
uintptr_t rt = (uintptr_t)__builtin_return_address(0);
|
||||
if (cases[1] == 64) {
|
||||
|
||||
for (uint64_t i = 0; i < cases[0]; i++) {
|
||||
|
||||
uintptr_t k = rt + i;
|
||||
k = (k >> 4) ^ (k << 8);
|
||||
k &= MAP_SIZE - 1;
|
||||
__lafl_cmp_map[k] = MAX(__lafl_cmp_map[k], (__builtin_popcountll(~(val ^ cases[i + 2]))));
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
for (uint64_t i = 0; i < cases[0]; i++) {
|
||||
|
||||
uintptr_t k = rt + i;
|
||||
k = (k >> 4) ^ (k << 8);
|
||||
k &= MAP_SIZE - 1;
|
||||
__lafl_cmp_map[k] = MAX(__lafl_cmp_map[k], (__builtin_popcount(~(val ^ cases[i + 2]))));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void *malloc(size_t size) {
|
||||
|
||||
uintptr_t k = (uintptr_t)__builtin_return_address(0);
|
||||
k = (k >> 4) ^ (k << 8);
|
||||
k &= MAP_SIZE - 1;
|
||||
__lafl_alloc_map[k] = MAX(__lafl_alloc_map[k], size);
|
||||
|
||||
// We cannot malloc in malloc.
|
||||
// Hence, even realloc(NULL, size) would loop in an optimized build.
|
||||
// We fall back to a stricter allocation function. Fingers crossed.
|
||||
void *ret = NULL;
|
||||
posix_memalign(&ret, 1<<6, size);
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
void *calloc(size_t nmemb, size_t size) {
|
||||
|
||||
size *= nmemb;
|
||||
|
||||
uintptr_t k = (uintptr_t)__builtin_return_address(0);
|
||||
k = (k >> 4) ^ (k << 8);
|
||||
k &= MAP_SIZE - 1;
|
||||
__lafl_alloc_map[k] = MAX(__lafl_alloc_map[k], size);
|
||||
|
||||
void *ret = NULL;
|
||||
posix_memalign(&ret, 1<<6, size);
|
||||
memset(ret, 0, size);
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
static void afl_libfuzzer_copy_args(int argc, char** argv, char** envp) {
|
||||
orig_argc = argc;
|
||||
orig_argv = argv;
|
||||
orig_envp = envp;
|
||||
}
|
||||
|
||||
__attribute__((section(".init_array"))) void (* p_afl_libfuzzer_copy_args)(int,char*[],char*[]) = &afl_libfuzzer_copy_args;
|
||||
|
||||
__attribute__((weak)) int LLVMFuzzerInitialize(int *argc, char ***argv);
|
||||
void afl_libfuzzer_main();
|
||||
|
||||
int afl_libfuzzer_init() {
|
||||
|
||||
if (LLVMFuzzerInitialize) {
|
||||
return LLVMFuzzerInitialize(&orig_argc, &orig_argv);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
#![allow(dead_code, mutable_transmutes, non_camel_case_types, non_snake_case,
|
||||
non_upper_case_globals, unused_assignments, unused_mut)]
|
||||
|
||||
use std::ptr;
|
||||
|
||||
pub const MAP_SIZE: usize = 65536;
|
||||
|
||||
extern "C" {
|
||||
/// __attribute__((weak)) int LLVMFuzzerInitialize(int *argc, char ***argv);
|
||||
fn LLVMFuzzerInitialize(argc: *mut libc::c_int,
|
||||
argv: *mut *mut *mut libc::c_char) -> libc::c_int;
|
||||
|
||||
/// int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
|
||||
pub fn LLVMFuzzerTestOneInput(data: *const u8, size: usize) -> i32;
|
||||
}
|
||||
|
||||
static mut orig_argc: libc::c_int = 0;
|
||||
static mut orig_argv: *mut *mut libc::c_char = ptr::null_mut();
|
||||
static mut orig_envp: *mut *mut libc::c_char = ptr::null_mut();
|
||||
|
||||
pub static mut edges_map: [u8; MAP_SIZE] = [0; MAP_SIZE];
|
||||
pub static mut cmp_map: [u8; MAP_SIZE] = [0; MAP_SIZE];
|
||||
pub static mut max_edges_size: usize = 0;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard(mut guard: *mut u32) {
|
||||
let mut pos: u32 = *guard;
|
||||
//uint16_t val = __lafl_edges_map[pos] + 1;
|
||||
//__lafl_edges_map[pos] = ((uint8_t) val) + (uint8_t) (val >> 8);
|
||||
edges_map[pos as usize] = 1 as u8;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard_init(mut start: *mut u32, mut stop: *mut u32) {
|
||||
if start == stop || *start != 0 { return }
|
||||
|
||||
while start < stop {
|
||||
max_edges_size += 1;
|
||||
*start = (max_edges_size & (MAP_SIZE -1)) as u32;
|
||||
start = start.offset(1);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn copy_args_init(mut argc: libc::c_int, mut argv: *mut *mut libc::c_char, mut envp: *mut *mut libc::c_char) {
|
||||
orig_argc = argc;
|
||||
orig_argv = argv;
|
||||
orig_envp = envp;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[link_section = ".init_array"]
|
||||
static mut p_copy_args_init: Option<unsafe extern "C" fn(_: libc::c_int, _: *mut *mut libc::c_char, _: *mut *mut libc::c_char) -> ()> = Some(copy_args_init);
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn afl_libfuzzer_init() -> libc::c_int {
|
||||
if Some(LLVMFuzzerInitialize).is_some() {
|
||||
LLVMFuzzerInitialize(&mut orig_argc, &mut orig_argv)
|
||||
} else {
|
||||
0 as libc::c_int
|
||||
}
|
||||
}
|
1
fuzzers/libfuzzer_stb_image/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
libpng-*
|
@ -1,31 +1,24 @@
|
||||
[package]
|
||||
name = "libfuzzer_libpng_cmpalloc"
|
||||
name = "libfuzzer_stb_image"
|
||||
version = "0.1.0"
|
||||
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
|
||||
#[profile.release]
|
||||
#lto = true
|
||||
#codegen-units = 1
|
||||
#opt-level = 3
|
||||
#debug = true
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[dependencies]
|
||||
libafl = { path = "../../libafl/" }
|
||||
libafl_targets = { path = "../../libafl_targets/", features = ["pcguard_edges", "libfuzzer"] }
|
||||
|
||||
[build-dependencies]
|
||||
cc = { version = "1.0", features = ["parallel"] }
|
||||
num_cpus = "1.0"
|
||||
|
||||
[dependencies]
|
||||
libafl = { path = "../../libafl/" }
|
||||
|
||||
[[example]]
|
||||
name = "libfuzzer_libpng_cmpalloc"
|
||||
path = "./src/fuzzer.rs"
|
||||
test = false
|
||||
bench = false
|
23
fuzzers/libfuzzer_stb_image/README.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Libfuzzer for stb_image
|
||||
|
||||
This folder contains an example fuzzer for stb_image, using LLMP for fast multi-process fuzzing and crash detection.
|
||||
It has been tested on Linux and Windows.
|
||||
|
||||
## Build
|
||||
|
||||
To build this example, run `cargo build --release`.
|
||||
This will build the the fuzzer (src/main.rs) with the libfuzzer compatibility layer and the SanitizerCoverage runtime functions for coverage feedback as a standalone binary.
|
||||
|
||||
Unlike the libpng example, in this example the harness (that entirely includes the program under test) is compiled in the `build.rs` file while building the crate, and linked with the fuzzer by cargo when producing the final binary, `target/release/libfuzzer_stb_image`.
|
||||
|
||||
## Run
|
||||
|
||||
The first time you run the binary (`target/release/libfuzzer_stb_image`), the broker will open a tcp port (currently on port `1337`), waiting for fuzzer clients to connect. This port is local and only used for the initial handshake. All further communication happens via shared map, to be independent of the kernel.
|
||||
|
||||
Each following execution will run a fuzzer client.
|
||||
As this example uses in-process fuzzing, we added a Restarting Event Manager (`setup_restarting_mgr`).
|
||||
This means each client will start itself again to listen for crashes and timeouts.
|
||||
By restarting the actual fuzzer, it can recover from these exit conditions.
|
||||
|
||||
In any real-world scenario, you should use `taskset` to pin each client to an empty CPU core, the lib does not pick an empty core automatically (yet).
|
||||
|
27
fuzzers/libfuzzer_stb_image/build.rs
Normal file
@ -0,0 +1,27 @@
|
||||
// build.rs
|
||||
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let out_dir = out_dir.to_string_lossy().to_string();
|
||||
|
||||
println!("cargo:rerun-if-changed=harness.c");
|
||||
|
||||
// Enforce clang for its -fsanitize-coverage support.
|
||||
std::env::set_var("CC", "clang");
|
||||
std::env::set_var("CXX", "clang++");
|
||||
|
||||
cc::Build::new()
|
||||
// Use sanitizer coverage to track the edges in the PUT
|
||||
.flag("-fsanitize-coverage=trace-pc-guard")
|
||||
// Take advantage of LTO (needs lld-link set in your cargo config)
|
||||
//.flag("-flto=thin")
|
||||
.flag("-Wno-sign-compare")
|
||||
.file("./harness.c")
|
||||
.compile("harness");
|
||||
|
||||
println!("cargo:rustc-link-search=native={}", &out_dir);
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
}
|
Before Width: | Height: | Size: 218 B After Width: | Height: | Size: 218 B |
Before Width: | Height: | Size: 376 B After Width: | Height: | Size: 376 B |
Before Width: | Height: | Size: 228 B After Width: | Height: | Size: 228 B |
Before Width: | Height: | Size: 427 B After Width: | Height: | Size: 427 B |
28
fuzzers/libfuzzer_stb_image/harness.c
Normal file
@ -0,0 +1,28 @@
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define STBI_ASSERT(x)
|
||||
#define STBI_NO_SIMD
|
||||
#define STBI_NO_LINEAR
|
||||
#define STBI_NO_STDIO
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
|
||||
#include "stb_image.h"
|
||||
|
||||
int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
int x, y, channels;
|
||||
|
||||
if(!stbi_info_from_memory(data, size, &x, &y, &channels)) return 0;
|
||||
|
||||
/* exit if the image is larger than ~80MB */
|
||||
if(y && x > (80000000 / 4) / y) return 0;
|
||||
|
||||
unsigned char *img = stbi_load_from_memory(data, size, &x, &y, &channels, 4);
|
||||
|
||||
free(img);
|
||||
|
||||
// if (x > 10000) free(img); // free crash
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,22 +1,21 @@
|
||||
//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts
|
||||
//! The example harness is built for libpng.
|
||||
//! The example harness is built for stb_image.
|
||||
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
#[cfg(unix)]
|
||||
use libafl::{
|
||||
bolts::{shmem::UnixShMem, tuples::tuple_list},
|
||||
bolts::tuples::tuple_list,
|
||||
corpus::{
|
||||
Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus,
|
||||
QueueCorpusScheduler,
|
||||
},
|
||||
events::setup_restarting_mgr,
|
||||
executors::{inprocess::InProcessExecutor, Executor, ExitKind},
|
||||
events::setup_restarting_mgr_std,
|
||||
executors::{inprocess::InProcessExecutor, ExitKind},
|
||||
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
|
||||
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer},
|
||||
inputs::Input,
|
||||
mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens},
|
||||
observers::{HitcountsMapObserver, StdMapObserver, TimeObserver},
|
||||
fuzzer::{Fuzzer, StdFuzzer},
|
||||
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
|
||||
mutators::token_mutations::Tokens,
|
||||
observers::{StdMapObserver, TimeObserver},
|
||||
stages::mutational::StdMutationalStage,
|
||||
state::{HasCorpus, HasMetadata, State},
|
||||
stats::SimpleStats,
|
||||
@ -24,38 +23,8 @@ use libafl::{
|
||||
Error,
|
||||
};
|
||||
|
||||
const MAP_SIZE: usize = 16 * 1024;
|
||||
use libafl_targets::{libfuzzer_initialize, libfuzzer_test_one_input, EDGES_MAP, MAX_EDGES_NUM};
|
||||
|
||||
/// We will interact with a C++ target, so use external c functionality
|
||||
#[cfg(unix)]
|
||||
extern "C" {
|
||||
/// int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
|
||||
fn LLVMFuzzerTestOneInput(data: *const u8, size: usize) -> i32;
|
||||
|
||||
// afl_libfuzzer_init calls LLVMFUzzerInitialize()
|
||||
fn afl_libfuzzer_init() -> i32;
|
||||
|
||||
static __lafl_edges_map: *mut u8;
|
||||
static __lafl_cmp_map: *mut u8;
|
||||
static __lafl_alloc_map: *mut usize;
|
||||
static __lafl_max_edges_size: u32;
|
||||
}
|
||||
|
||||
/// The wrapped harness function, calling out to the LLVM-style harness
|
||||
#[cfg(unix)]
|
||||
fn harness<E, I>(_executor: &E, buf: &[u8]) -> ExitKind
|
||||
where
|
||||
E: Executor<I>,
|
||||
I: Input,
|
||||
{
|
||||
// println!("{:?}", buf);
|
||||
unsafe {
|
||||
LLVMFuzzerTestOneInput(buf.as_ptr(), buf.len());
|
||||
}
|
||||
ExitKind::Ok
|
||||
}
|
||||
|
||||
/// The main fn, usually parsing parameters, and starting the fuzzer
|
||||
pub fn main() {
|
||||
// Registry the metadata types used in this fuzzer
|
||||
// Needed only on no_std
|
||||
@ -73,43 +42,28 @@ pub fn main() {
|
||||
.expect("An error occurred while fuzzing");
|
||||
}
|
||||
|
||||
/// Not supported on windows right now
|
||||
#[cfg(windows)]
|
||||
fn fuzz(_corpus_dirs: Vec<PathBuf>, _objective_dir: PathBuf, _broker_port: u16) -> Result<(), ()> {
|
||||
todo!("Example not supported on Windows");
|
||||
}
|
||||
|
||||
/// The actual fuzzer
|
||||
#[cfg(unix)]
|
||||
fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> {
|
||||
// 'While the stats are state, they are usually used in the broker - which is likely never restarted
|
||||
let stats = SimpleStats::new(|s| println!("{}", s));
|
||||
|
||||
// The restarting state will spawn the same process again as child, then restarted it each time it crashes.
|
||||
let (state, mut restarting_mgr) =
|
||||
match setup_restarting_mgr::<_, _, UnixShMem, _>(stats, broker_port) {
|
||||
Ok(res) => res,
|
||||
Err(err) => match err {
|
||||
Error::ShuttingDown => {
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
panic!("Failed to setup the restarter: {}", err);
|
||||
}
|
||||
},
|
||||
};
|
||||
let (state, mut restarting_mgr) = match setup_restarting_mgr_std(stats, broker_port) {
|
||||
Ok(res) => res,
|
||||
Err(err) => match err {
|
||||
Error::ShuttingDown => {
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
panic!("Failed to setup the restarter: {}", err);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Create an observation channel using the coverage map
|
||||
let edges_observer = HitcountsMapObserver::new(unsafe {
|
||||
StdMapObserver::new_from_ptr("edges", __lafl_edges_map, __lafl_max_edges_size as usize)
|
||||
});
|
||||
|
||||
// Create an observation channel using the cmp map
|
||||
let cmps_observer = unsafe { StdMapObserver::new_from_ptr("cmps", __lafl_cmp_map, MAP_SIZE) };
|
||||
|
||||
// Create an observation channel using the allocations map
|
||||
let allocs_observer =
|
||||
unsafe { StdMapObserver::new_from_ptr("allocs", __lafl_alloc_map, MAP_SIZE) };
|
||||
// We don't use the hitcounts (see the Cargo.toml, we use pcguard_edges)
|
||||
let edges_observer =
|
||||
StdMapObserver::new("edges", unsafe { &mut EDGES_MAP }, unsafe { MAX_EDGES_NUM });
|
||||
|
||||
// If not restarting, create a State from scratch
|
||||
let mut state = state.unwrap_or_else(|| {
|
||||
@ -121,8 +75,6 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
|
||||
// Feedbacks to rate the interestingness of an input
|
||||
tuple_list!(
|
||||
MaxMapFeedback::new_with_observer_track(&edges_observer, true, false),
|
||||
MaxMapFeedback::new_with_observer(&cmps_observer),
|
||||
MaxMapFeedback::new_with_observer(&allocs_observer),
|
||||
TimeFeedback::new()
|
||||
),
|
||||
// Corpus in which we store solutions (crashes in this example),
|
||||
@ -147,44 +99,41 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
|
||||
}
|
||||
|
||||
// Setup a basic mutator with a mutational stage
|
||||
let mutator = HavocBytesMutator::default();
|
||||
let mutator = StdScheduledMutator::new(havoc_mutations());
|
||||
let stage = StdMutationalStage::new(mutator);
|
||||
|
||||
// A fuzzer with just one stage and a minimization+queue policy to get testcasess from the corpus
|
||||
let mut fuzzer = StdFuzzer::new(tuple_list!(stage));
|
||||
|
||||
// A minimization+queue policy to get testcasess from the corpus
|
||||
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
|
||||
let fuzzer = StdFuzzer::new(scheduler, tuple_list!(stage));
|
||||
|
||||
// The wrapped harness function, calling out to the LLVM-style harness
|
||||
let mut harness = |buf: &[u8]| {
|
||||
libfuzzer_test_one_input(buf);
|
||||
ExitKind::Ok
|
||||
};
|
||||
|
||||
// Create the executor for an in-process function with just one observer for edge coverage
|
||||
let mut executor = InProcessExecutor::new(
|
||||
"in-process(edges,cmps,allocs)",
|
||||
harness,
|
||||
tuple_list!(
|
||||
edges_observer,
|
||||
cmps_observer,
|
||||
allocs_observer,
|
||||
TimeObserver::new("time")
|
||||
),
|
||||
"in-process(edges,time)",
|
||||
&mut harness,
|
||||
tuple_list!(edges_observer, TimeObserver::new("time")),
|
||||
&mut state,
|
||||
&mut restarting_mgr,
|
||||
)?;
|
||||
|
||||
// The actual target run starts here.
|
||||
// Call LLVMFUzzerInitialize() if present.
|
||||
unsafe {
|
||||
if afl_libfuzzer_init() == -1 {
|
||||
println!("Warning: LLVMFuzzerInitialize failed with -1")
|
||||
}
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if libfuzzer_initialize(&args) == -1 {
|
||||
println!("Warning: LLVMFuzzerInitialize failed with -1")
|
||||
}
|
||||
|
||||
// In case the corpus is empty (on first run), reset
|
||||
if state.corpus().count() < 1 {
|
||||
state
|
||||
.load_initial_inputs(
|
||||
&mut executor,
|
||||
&mut restarting_mgr,
|
||||
fuzzer.scheduler(),
|
||||
&corpus_dirs,
|
||||
)
|
||||
.load_initial_inputs(&mut executor, &mut restarting_mgr, &scheduler, &corpus_dirs)
|
||||
.expect(&format!(
|
||||
"Failed to load initial corpus at {:?}",
|
||||
&corpus_dirs
|
||||
@ -192,7 +141,7 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
|
||||
println!("We imported {} inputs from disk.", state.corpus().count());
|
||||
}
|
||||
|
||||
fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr)?;
|
||||
fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr, &scheduler)?;
|
||||
|
||||
// Never reached
|
||||
Ok(())
|
7762
fuzzers/libfuzzer_stb_image/stb_image.h
Normal file
@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "qemufuzzer"
|
||||
version = "0.1.0"
|
||||
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[dependencies]
|
||||
afl = { path = "../../afl/" }
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib", "cdylib"]
|
@ -1,12 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
cargo build --release
|
||||
|
||||
git submodule init
|
||||
git submodule update qemu_fuzz
|
||||
|
||||
cd qemu-fuzz
|
||||
|
||||
./build_qemu_fuzz.sh ../target/release/libqemufuzzer.a
|
||||
|
||||
cp build/qemu-x86_64 ../qemu_fuzz
|
@ -1 +0,0 @@
|
||||
Subproject commit 6f719f6aedc9c199d7b99ef42c532a9a20605ff3
|
@ -1,102 +0,0 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use afl::corpus::InMemoryCorpus;
|
||||
use afl::engines::Engine;
|
||||
use afl::engines::Fuzzer;
|
||||
use afl::engines::State;
|
||||
use afl::engines::StdFuzzer;
|
||||
use afl::events::{LlmpEventManager, SimpleStats};
|
||||
use afl::executors::inmemory::InProcessExecutor;
|
||||
use afl::executors::{Executor, ExitKind};
|
||||
use afl::feedbacks::MaxMapFeedback;
|
||||
use afl::generators::RandPrintablesGenerator;
|
||||
use afl::mutators::scheduled::HavocBytesMutator;
|
||||
use afl::mutators::HasMaxSize;
|
||||
use afl::observers::VariableMapObserver;
|
||||
use afl::stages::mutational::StdMutationalStage;
|
||||
use afl::tuples::tuple_list;
|
||||
use afl::utils::StdRand;
|
||||
|
||||
use core::cmp::min;
|
||||
|
||||
mod regs;
|
||||
use regs::*;
|
||||
|
||||
const FUZZ_MAP_SIZE: usize = 1048576;
|
||||
|
||||
type TargetULong = u64;
|
||||
|
||||
extern "C" {
|
||||
fn fuzz_run_target(regs: *const x86_64_regs);
|
||||
fn fuzz_write_mem(addr: TargetULong, buf: *const u8, size: usize);
|
||||
// fn fuzz_read_mem(addr: TargetULong, buf: *const u8, size: usize);
|
||||
|
||||
static fuzz_start_regs: x86_64_regs;
|
||||
static mut fuzz_hitcounts_map: [u8; FUZZ_MAP_SIZE];
|
||||
static mut fuzz_edges_id: usize;
|
||||
}
|
||||
|
||||
fn harness<I>(_executor: &dyn Executor<I>, buf: &[u8]) -> ExitKind {
|
||||
unsafe {
|
||||
let mut regs = fuzz_start_regs.clone();
|
||||
let len = min(buf.len(), 4096);
|
||||
regs.rsi = len as u64;
|
||||
fuzz_write_mem(regs.rdi, buf.as_ptr(), len);
|
||||
fuzz_run_target(®s);
|
||||
}
|
||||
ExitKind::Ok
|
||||
}
|
||||
|
||||
const NAME_COV_MAP: &str = "cov_map";
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fuzz_main_loop() {
|
||||
let mut rand = StdRand::new(0);
|
||||
|
||||
let mut corpus = InMemoryCorpus::new();
|
||||
let mut generator = RandPrintablesGenerator::new(32);
|
||||
|
||||
let stats = SimpleStats::new(|s| println!("{}", s));
|
||||
let mut mgr = LlmpEventManager::new_on_port_std(1337, stats).unwrap();
|
||||
if mgr.is_broker() {
|
||||
println!("Doing broker things.");
|
||||
mgr.broker_loop().unwrap();
|
||||
}
|
||||
println!("We're a client, let's fuzz :)");
|
||||
|
||||
let edges_observer =
|
||||
VariableMapObserver::new(&NAME_COV_MAP, unsafe { &mut fuzz_hitcounts_map }, unsafe {
|
||||
&fuzz_edges_id
|
||||
});
|
||||
let edges_feedback = MaxMapFeedback::new_with_observer(&NAME_COV_MAP, &edges_observer);
|
||||
|
||||
let executor = InProcessExecutor::new("QEMUFuzzer", harness, tuple_list!(edges_observer))?;
|
||||
let mut state = State::new(tuple_list!(edges_feedback));
|
||||
|
||||
let mut engine = Engine::new(executor);
|
||||
|
||||
state
|
||||
.generate_initial_inputs(
|
||||
&mut rand,
|
||||
&mut corpus,
|
||||
&mut generator,
|
||||
&mut engine,
|
||||
&mut mgr,
|
||||
4,
|
||||
)
|
||||
.expect("Failed to load initial inputs");
|
||||
|
||||
let mut mutator = HavocBytesMutator::new_default();
|
||||
mutator.set_max_size(4096);
|
||||
|
||||
let stage = StdMutationalStage::new(mutator);
|
||||
let mut fuzzer = StdFuzzer::new(tuple_list!(stage));
|
||||
|
||||
fuzzer
|
||||
.fuzz_loop(&mut rand, &mut state, &mut corpus, &mut engine, &mut mgr)
|
||||
.expect("Fuzzer fatal error");
|
||||
#[cfg(feature = "std")]
|
||||
println!("OK");
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
/* Generated by hand by Fioraldi bindgen */
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct x86_regs {
|
||||
pub eax: u32,
|
||||
pub ebx: u32,
|
||||
pub ecx: u32,
|
||||
pub edx: u32,
|
||||
pub edi: u32,
|
||||
pub esi: u32,
|
||||
pub ebp: u32,
|
||||
pub eip: u32,
|
||||
pub esp: u32,
|
||||
pub eflags: u32,
|
||||
pub xmm_regs: [[u8; 8usize]; 16usize],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct x86_64_regs {
|
||||
pub rax: u64,
|
||||
pub rbx: u64,
|
||||
pub rcx: u64,
|
||||
pub rdx: u64,
|
||||
pub rdi: u64,
|
||||
pub rsi: u64,
|
||||
pub rbp: u64,
|
||||
pub r8: u64,
|
||||
pub r9: u64,
|
||||
pub r10: u64,
|
||||
pub r11: u64,
|
||||
pub r12: u64,
|
||||
pub r13: u64,
|
||||
pub r14: u64,
|
||||
pub r15: u64,
|
||||
pub rip: u64,
|
||||
pub rsp: u64,
|
||||
pub rflags: u64,
|
||||
pub zmm_regs: [[u8; 32usize]; 64usize],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct arm_regs {
|
||||
pub r0: u32,
|
||||
pub r1: u32,
|
||||
pub r2: u32,
|
||||
pub r3: u32,
|
||||
pub r4: u32,
|
||||
pub r5: u32,
|
||||
pub r6: u32,
|
||||
pub r7: u32,
|
||||
pub r8: u32,
|
||||
pub r9: u32,
|
||||
pub r10: u32,
|
||||
pub r11: u32,
|
||||
pub r12: u32,
|
||||
pub r13: u32,
|
||||
pub r14: u32,
|
||||
pub r15: u32,
|
||||
pub cpsr: u32,
|
||||
pub vfp_zregs: [[u8; 32usize]; 16usize],
|
||||
pub vfp_xregs: [u32; 16usize],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct arm64_regs {
|
||||
pub x0: u64,
|
||||
pub x1: u64,
|
||||
pub x2: u64,
|
||||
pub x3: u64,
|
||||
pub x4: u64,
|
||||
pub x5: u64,
|
||||
pub x6: u64,
|
||||
pub x7: u64,
|
||||
pub x8: u64,
|
||||
pub x9: u64,
|
||||
pub x10: u64,
|
||||
pub x11: u64,
|
||||
pub x12: u64,
|
||||
pub x13: u64,
|
||||
pub x14: u64,
|
||||
pub x15: u64,
|
||||
pub x16: u64,
|
||||
pub x17: u64,
|
||||
pub x18: u64,
|
||||
pub x19: u64,
|
||||
pub x20: u64,
|
||||
pub x21: u64,
|
||||
pub x22: u64,
|
||||
pub x23: u64,
|
||||
pub x24: u64,
|
||||
pub x25: u64,
|
||||
pub x26: u64,
|
||||
pub x27: u64,
|
||||
pub x28: u64,
|
||||
pub x29: u64,
|
||||
pub x30: u64,
|
||||
pub x31: u64,
|
||||
pub pc: u64,
|
||||
pub cpsr: u32,
|
||||
pub vfp_zregs: [[u8; 32usize]; 256usize],
|
||||
pub vfp_pregs: [[u8; 17usize]; 32usize],
|
||||
pub vfp_xregs: [u32; 16usize],
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
int target_func(const uint8_t *buf, size_t size) {
|
||||
|
||||
/*printf("BUF (%ld): ", size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
printf("%02X", buf[i]);
|
||||
}
|
||||
printf("\n");*/
|
||||
|
||||
if (size == 0) return 0;
|
||||
|
||||
switch (buf[0]) {
|
||||
|
||||
case 1:
|
||||
if (buf[1] == 0x44) {
|
||||
//__builtin_trap();
|
||||
return 8;
|
||||
}
|
||||
|
||||
break;
|
||||
case 0xff:
|
||||
if (buf[2] == 0xff) {
|
||||
if (buf[1] == 0x44) {
|
||||
//*(char *)(0xdeadbeef) = 1;
|
||||
return 9;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
return target_func(Data, Size);
|
||||
}
|
@ -2,6 +2,11 @@
|
||||
name = "libafl"
|
||||
version = "0.1.0"
|
||||
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
|
||||
description = "Slot your own fuzzers together and extend their features using Rust"
|
||||
documentation = "https://docs.rs/libafl"
|
||||
repository = "https://github.com/AFLplusplus/LibAFL/"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["fuzzing", "testing", "security"]
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
|
||||
@ -13,7 +18,6 @@ ahash = "0.6.1" # another hash
|
||||
fxhash = "0.2.1" # yet another hash
|
||||
xxhash-rust = { version = "0.8.0", features = ["const_xxh3", "xxh3"] } # xxh3 hashing for rust
|
||||
serde_json = "1.0.60"
|
||||
|
||||
num_cpus = "1.0" # cpu count, for llmp example
|
||||
|
||||
[[bench]]
|
||||
@ -30,12 +34,13 @@ harness = false
|
||||
#debug = true
|
||||
|
||||
[features]
|
||||
default = ["std", "anymapdbg", "derive"]
|
||||
default = ["std", "anymap_debug", "derive", "llmp_compression"]
|
||||
std = [] # print, sharedmap, ... support
|
||||
runtime = [] # a runtime for clang inmem-executor
|
||||
anymapdbg = ["serde_json"] # uses serde_json to Debug the anymap trait. Disable for smaller footprint.
|
||||
anymap_debug = ["serde_json"] # uses serde_json to Debug the anymap trait. Disable for smaller footprint.
|
||||
derive = ["libafl_derive"] # provide derive(SerdeAny) macro.
|
||||
llmp_small_maps = [] # reduces initial map size for llmp
|
||||
llmp_debug = ["backtrace"] # Enables debug output for LLMP
|
||||
llmp_compression = [] #llmp compression using GZip
|
||||
|
||||
[[example]]
|
||||
name = "llmp_test"
|
||||
@ -46,7 +51,7 @@ required-features = ["std"]
|
||||
tuple_list = "0.1.2"
|
||||
hashbrown = { version = "0.9", features = ["serde", "ahash-compile-time-rng"] } # A faster hashmap, nostd compatible
|
||||
num = "*"
|
||||
xxhash-rust = { version = "0.8.0", features = ["xxh3"] } # xxh3 hashing for rust
|
||||
xxhash-rust = { version = "0.8.0", features = ["xxh3", "const_xxh3"] } # xxh3 hashing for rust
|
||||
serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib
|
||||
erased-serde = "0.3.12"
|
||||
postcard = { version = "0.5.1", features = ["alloc"] } # no_std compatible serde serialization fromat
|
||||
@ -54,13 +59,22 @@ static_assertions = "1.1.0"
|
||||
ctor = "*"
|
||||
libafl_derive = { version = "*", optional = true, path = "../libafl_derive" }
|
||||
serde_json = { version = "1.0", optional = true, default-features = false, features = ["alloc"] } # an easy way to debug print SerdeAnyMap
|
||||
#TODO: for llmp brotli = { version = "3.3.0", default-features = false } # brotli compression
|
||||
compression = { version = "0.1.5" }
|
||||
num_enum = "0.5.1"
|
||||
spin = "0.9.0"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
backtrace = { version = "0.3", optional = true, default-features = false, features = ["std", "libbacktrace"] } # for llmp_debug
|
||||
|
||||
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||
backtrace = { version = "0.3", optional = true } # for llmp_debug
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2" # For (*nix) libc
|
||||
nix = "0.20.0"
|
||||
uds = "0.2.3"
|
||||
lock_api = "0.4.3"
|
||||
regex = "1.4.5"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows = "0.4.0"
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! special handling to build and link libafl
|
||||
|
||||
fn main() {
|
||||
#[cfg(target_os = "windows")]
|
||||
windows::build!(
|
||||
|
@ -11,7 +11,10 @@ use std::{thread, time};
|
||||
use libafl::bolts::llmp::Tag;
|
||||
#[cfg(all(unix, feature = "std"))]
|
||||
use libafl::{
|
||||
bolts::{llmp, shmem::UnixShMem},
|
||||
bolts::{
|
||||
llmp,
|
||||
shmem::{ShMemProvider, StdShMemProvider},
|
||||
},
|
||||
Error,
|
||||
};
|
||||
|
||||
@ -21,7 +24,8 @@ const _TAG_1MEG_V1: Tag = 0xB1111161;
|
||||
|
||||
#[cfg(all(unix, feature = "std"))]
|
||||
fn adder_loop(port: u16) -> ! {
|
||||
let mut client = llmp::LlmpClient::<UnixShMem>::create_attach_to_tcp(port).unwrap();
|
||||
let shmem_provider = StdShMemProvider::new().unwrap();
|
||||
let mut client = llmp::LlmpClient::create_attach_to_tcp(shmem_provider, port).unwrap();
|
||||
let mut last_result: u32 = 0;
|
||||
let mut current_result: u32 = 0;
|
||||
loop {
|
||||
@ -63,7 +67,8 @@ fn adder_loop(port: u16) -> ! {
|
||||
|
||||
#[cfg(all(unix, feature = "std"))]
|
||||
fn large_msg_loop(port: u16) -> ! {
|
||||
let mut client = llmp::LlmpClient::<UnixShMem>::create_attach_to_tcp(port).unwrap();
|
||||
let mut client =
|
||||
llmp::LlmpClient::create_attach_to_tcp(StdShMemProvider::new().unwrap(), port).unwrap();
|
||||
|
||||
let meg_buf = [1u8; 1 << 20];
|
||||
|
||||
@ -78,6 +83,7 @@ fn large_msg_loop(port: u16) -> ! {
|
||||
fn broker_message_hook(
|
||||
client_id: u32,
|
||||
tag: llmp::Tag,
|
||||
_flags: llmp::Flag,
|
||||
message: &[u8],
|
||||
) -> Result<llmp::LlmpMsgHookResult, Error> {
|
||||
match tag {
|
||||
@ -124,7 +130,7 @@ fn main() {
|
||||
|
||||
match mode.as_str() {
|
||||
"broker" => {
|
||||
let mut broker = llmp::LlmpBroker::<UnixShMem>::new().unwrap();
|
||||
let mut broker = llmp::LlmpBroker::new(StdShMemProvider::new().unwrap()).unwrap();
|
||||
broker
|
||||
.launch_listener(llmp::Listener::Tcp(
|
||||
std::net::TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap(),
|
||||
@ -133,7 +139,9 @@ fn main() {
|
||||
broker.loop_forever(&mut broker_message_hook, Some(Duration::from_millis(5)))
|
||||
}
|
||||
"ctr" => {
|
||||
let mut client = llmp::LlmpClient::<UnixShMem>::create_attach_to_tcp(port).unwrap();
|
||||
let mut client =
|
||||
llmp::LlmpClient::create_attach_to_tcp(StdShMemProvider::new().unwrap(), port)
|
||||
.unwrap();
|
||||
let mut counter: u32 = 0;
|
||||
loop {
|
||||
counter = counter.wrapping_add(1);
|
||||
|
@ -1,2 +1,4 @@
|
||||
//! Generated bindings
|
||||
|
||||
#[cfg(all(windows, feature = "std"))]
|
||||
::windows::include_bindings!();
|
||||
|
75
libafl/src/bolts/compress.rs
Normal file
@ -0,0 +1,75 @@
|
||||
//! Compression of events passed between a broker and clients.
|
||||
//! Currently we use the gzip compression algorithm for its fast decompression performance.
|
||||
|
||||
#[cfg(feature = "llmp_compression")]
|
||||
use crate::Error;
|
||||
use alloc::vec::Vec;
|
||||
use compression::prelude::*;
|
||||
use core::fmt::Debug;
|
||||
|
||||
/// Compression for your stream compression needs.
|
||||
#[derive(Debug)]
|
||||
pub struct GzipCompressor {
|
||||
/// If less bytes than threshold are being passed to `compress`, the payload is not getting compressed.
|
||||
threshold: usize,
|
||||
}
|
||||
|
||||
impl GzipCompressor {
|
||||
/// If the buffer is at lest larger as large as the `threshold` value, we compress the buffer.
|
||||
/// When given a `threshold` of `0`, the `GzipCompressor` will always compress.
|
||||
pub fn new(threshold: usize) -> Self {
|
||||
GzipCompressor { threshold }
|
||||
}
|
||||
}
|
||||
|
||||
impl GzipCompressor {
|
||||
/// Compression.
|
||||
/// If the buffer is smaller than the threshold of this compressor, `None` will be returned.
|
||||
/// Else, the buffer is compressed.
|
||||
pub fn compress(&self, buf: &[u8]) -> Result<Option<Vec<u8>>, Error> {
|
||||
if buf.len() >= self.threshold {
|
||||
//compress if the buffer is large enough
|
||||
let compressed = buf
|
||||
.iter()
|
||||
.cloned()
|
||||
.encode(&mut GZipEncoder::new(), Action::Finish)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(Some(compressed))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decompression.
|
||||
/// Flag is used to indicate if it's compressed or not
|
||||
pub fn decompress(&self, buf: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
Ok(buf
|
||||
.iter()
|
||||
.cloned()
|
||||
.decode(&mut GZipDecoder::new())
|
||||
.collect::<Result<Vec<_>, _>>()?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::bolts::compress::GzipCompressor;
|
||||
|
||||
#[test]
|
||||
fn test_compression() {
|
||||
let compressor = GzipCompressor::new(1);
|
||||
assert!(
|
||||
compressor
|
||||
.decompress(&compressor.compress(&[1u8; 1024]).unwrap().unwrap())
|
||||
.unwrap()
|
||||
== vec![1u8; 1024]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_threshold() {
|
||||
let compressor = GzipCompressor::new(1024);
|
||||
assert!(compressor.compress(&[1u8; 1023]).unwrap().is_none());
|
||||
assert!(compressor.compress(&[1u8; 1024]).unwrap().is_some());
|
||||
}
|
||||
}
|
@ -1,6 +1,10 @@
|
||||
//! Bolts are no conceptual fuzzing elements, but they keep libafl-based fuzzers together.
|
||||
|
||||
pub mod bindings;
|
||||
|
||||
#[cfg(feature = "llmp_compression")]
|
||||
pub mod compress;
|
||||
|
||||
pub mod llmp;
|
||||
pub mod os;
|
||||
pub mod ownedref;
|
||||
|
436
libafl/src/bolts/os/ashmem_server.rs
Normal file
@ -0,0 +1,436 @@
|
||||
/*!
|
||||
On Android, we can only share maps between processes by serializing fds over sockets.
|
||||
Hence, the `ashmem_server` keeps track of existing maps, creates new maps for clients,
|
||||
and forwards them over unix domain sockets.
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
bolts::shmem::{
|
||||
unix_shmem::ashmem::{AshmemShMem, AshmemShMemProvider},
|
||||
ShMem, ShMemDescription, ShMemId, ShMemProvider,
|
||||
},
|
||||
Error,
|
||||
};
|
||||
use core::mem::ManuallyDrop;
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
io::{Read, Write},
|
||||
rc::Rc,
|
||||
sync::{Arc, Condvar, Mutex},
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "std", unix))]
|
||||
use nix::poll::{poll, PollFd, PollFlags};
|
||||
|
||||
#[cfg(all(feature = "std", unix))]
|
||||
use std::{
|
||||
os::unix::{
|
||||
io::{AsRawFd, RawFd},
|
||||
net::{UnixListener, UnixStream},
|
||||
},
|
||||
thread,
|
||||
};
|
||||
|
||||
#[cfg(all(unix, feature = "std"))]
|
||||
use uds::{UnixListenerExt, UnixSocketAddr, UnixStreamExt};
|
||||
|
||||
const ASHMEM_SERVER_NAME: &str = "@ashmem_server";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ServedShMemProvider {
|
||||
stream: UnixStream,
|
||||
inner: AshmemShMemProvider,
|
||||
id: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ServedShMem {
|
||||
inner: ManuallyDrop<AshmemShMem>,
|
||||
server_fd: i32,
|
||||
}
|
||||
|
||||
impl ShMem for ServedShMem {
|
||||
fn id(&self) -> ShMemId {
|
||||
let client_id = self.inner.id();
|
||||
ShMemId::from_string(&format!("{}:{}", self.server_fd, client_id.to_string()))
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
|
||||
fn map(&self) -> &[u8] {
|
||||
self.inner.map()
|
||||
}
|
||||
|
||||
fn map_mut(&mut self) -> &mut [u8] {
|
||||
self.inner.map_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl ServedShMemProvider {
|
||||
/// Send a request to the server, and wait for a response
|
||||
#[allow(clippy::similar_names)] // id and fd
|
||||
fn send_receive(&mut self, request: AshmemRequest) -> (i32, i32) {
|
||||
let body = postcard::to_allocvec(&request).unwrap();
|
||||
|
||||
let header = (body.len() as u32).to_be_bytes();
|
||||
let mut message = header.to_vec();
|
||||
message.extend(body);
|
||||
|
||||
self.stream
|
||||
.write_all(&message)
|
||||
.expect("Failed to send message");
|
||||
|
||||
let mut shm_slice = [0u8; 20];
|
||||
let mut fd_buf = [-1; 1];
|
||||
self.stream
|
||||
.recv_fds(&mut shm_slice, &mut fd_buf)
|
||||
.expect("Did not receive a response");
|
||||
|
||||
let server_id = ShMemId::from_slice(&shm_slice);
|
||||
let server_id_str = server_id.to_string();
|
||||
let server_fd: i32 = server_id_str.parse().unwrap();
|
||||
(server_fd, fd_buf[0])
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ServedShMemProvider {
|
||||
fn default() -> Self {
|
||||
Self::new().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ServedShMemProvider {
|
||||
fn clone(&self) -> Self {
|
||||
Self::new().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl ShMemProvider for ServedShMemProvider {
|
||||
type Mem = ServedShMem;
|
||||
|
||||
/// Connect to the server and return a new ServedShMemProvider
|
||||
fn new() -> Result<Self, Error> {
|
||||
let mut res = Self {
|
||||
stream: UnixStream::connect_to_unix_addr(
|
||||
&UnixSocketAddr::new(ASHMEM_SERVER_NAME).unwrap(),
|
||||
)?,
|
||||
inner: AshmemShMemProvider::new()?,
|
||||
id: -1,
|
||||
};
|
||||
let (id, _) = res.send_receive(AshmemRequest::Hello(None));
|
||||
res.id = id;
|
||||
Ok(res)
|
||||
}
|
||||
fn new_map(&mut self, map_size: usize) -> Result<Self::Mem, crate::Error> {
|
||||
let (server_fd, client_fd) = self.send_receive(AshmemRequest::NewMap(map_size));
|
||||
|
||||
Ok(ServedShMem {
|
||||
inner: ManuallyDrop::new(
|
||||
self.inner
|
||||
.from_id_and_size(ShMemId::from_string(&format!("{}", client_fd)), map_size)?,
|
||||
),
|
||||
server_fd,
|
||||
})
|
||||
}
|
||||
|
||||
fn from_id_and_size(&mut self, id: ShMemId, size: usize) -> Result<Self::Mem, Error> {
|
||||
let parts = id.to_string().split(':').collect::<Vec<&str>>();
|
||||
let server_id_str = parts.get(0).unwrap();
|
||||
let (server_fd, client_fd) = self.send_receive(AshmemRequest::ExistingMap(
|
||||
ShMemDescription::from_string_and_size(server_id_str, size),
|
||||
));
|
||||
Ok(ServedShMem {
|
||||
inner: ManuallyDrop::new(
|
||||
self.inner
|
||||
.from_id_and_size(ShMemId::from_string(&format!("{}", client_fd)), size)?,
|
||||
),
|
||||
server_fd,
|
||||
})
|
||||
}
|
||||
|
||||
fn post_fork(&mut self) {
|
||||
self.stream =
|
||||
UnixStream::connect_to_unix_addr(&UnixSocketAddr::new(ASHMEM_SERVER_NAME).unwrap())
|
||||
.expect("Unable to reconnect to the ashmem service");
|
||||
let (id, _) = self.send_receive(AshmemRequest::Hello(Some(self.id)));
|
||||
self.id = id;
|
||||
}
|
||||
|
||||
fn release_map(&mut self, map: &mut Self::Mem) {
|
||||
let (refcount, _) = self.send_receive(AshmemRequest::Deregister(map.server_fd));
|
||||
if refcount == 0 {
|
||||
unsafe {
|
||||
ManuallyDrop::drop(&mut map.inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A request sent to the ShMem server to receive a fd to a shared map
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum AshmemRequest {
|
||||
/// Register a new map with a given size.
|
||||
NewMap(usize),
|
||||
/// Another client already has a map with this description mapped.
|
||||
ExistingMap(ShMemDescription),
|
||||
/// A client tells us it unregisters the previously allocated map
|
||||
Deregister(i32),
|
||||
/// A message that tells us hello, and optionally which other client we were created from, we
|
||||
/// return a client id.
|
||||
Hello(Option<i32>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AshmemClient {
|
||||
stream: UnixStream,
|
||||
maps: HashMap<i32, Vec<Rc<RefCell<AshmemShMem>>>>,
|
||||
}
|
||||
|
||||
impl AshmemClient {
|
||||
fn new(stream: UnixStream) -> Self {
|
||||
Self {
|
||||
stream,
|
||||
maps: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AshmemService {
|
||||
provider: AshmemShMemProvider,
|
||||
clients: HashMap<RawFd, AshmemClient>,
|
||||
all_maps: HashMap<i32, Rc<RefCell<AshmemShMem>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum AshmemResponse {
|
||||
Mapping(Rc<RefCell<AshmemShMem>>),
|
||||
Id(i32),
|
||||
RefCount(u32),
|
||||
}
|
||||
|
||||
impl AshmemService {
|
||||
/// Create a new AshMem service
|
||||
fn new() -> Result<Self, Error> {
|
||||
Ok(AshmemService {
|
||||
provider: AshmemShMemProvider::new()?,
|
||||
clients: HashMap::new(),
|
||||
all_maps: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Read and handle the client request, send the answer over unix fd.
|
||||
fn handle_request(&mut self, client_id: RawFd) -> Result<AshmemResponse, Error> {
|
||||
let request = self.read_request(client_id)?;
|
||||
|
||||
//println!("got ashmem client: {}, request:{:?}", client_id, request);
|
||||
// Handle the client request
|
||||
let response = match request {
|
||||
AshmemRequest::Hello(other_id) => {
|
||||
if let Some(other_id) = other_id {
|
||||
if other_id != client_id {
|
||||
// remove temporarily
|
||||
let other_client = self.clients.remove(&other_id);
|
||||
let client = self.clients.get_mut(&client_id).unwrap();
|
||||
for (id, map) in other_client.as_ref().unwrap().maps.iter() {
|
||||
client.maps.insert(*id, map.clone());
|
||||
}
|
||||
self.clients.insert(other_id, other_client.unwrap());
|
||||
}
|
||||
};
|
||||
Ok(AshmemResponse::Id(client_id))
|
||||
}
|
||||
AshmemRequest::NewMap(map_size) => Ok(AshmemResponse::Mapping(Rc::new(RefCell::new(
|
||||
self.provider.new_map(map_size)?,
|
||||
)))),
|
||||
AshmemRequest::ExistingMap(description) => {
|
||||
let client = self.clients.get_mut(&client_id).unwrap();
|
||||
if client.maps.contains_key(&description.id.to_int()) {
|
||||
Ok(AshmemResponse::Mapping(
|
||||
client
|
||||
.maps
|
||||
.get_mut(&description.id.to_int())
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.first()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.clone(),
|
||||
))
|
||||
} else if self.all_maps.contains_key(&description.id.to_int()) {
|
||||
Ok(AshmemResponse::Mapping(
|
||||
self.all_maps
|
||||
.get_mut(&description.id.to_int())
|
||||
.unwrap()
|
||||
.clone(),
|
||||
))
|
||||
} else {
|
||||
let new_rc =
|
||||
Rc::new(RefCell::new(self.provider.from_description(description)?));
|
||||
self.all_maps
|
||||
.insert(description.id.to_int(), new_rc.clone());
|
||||
Ok(AshmemResponse::Mapping(new_rc))
|
||||
}
|
||||
}
|
||||
AshmemRequest::Deregister(map_id) => {
|
||||
let client = self.clients.get_mut(&client_id).unwrap();
|
||||
let map = client.maps.entry(map_id).or_default().pop().unwrap();
|
||||
Ok(AshmemResponse::RefCount(Rc::strong_count(&map) as u32))
|
||||
}
|
||||
};
|
||||
//println!("send ashmem client: {}, response: {:?}", client_id, &response);
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
fn read_request(&mut self, client_id: RawFd) -> Result<AshmemRequest, Error> {
|
||||
let client = self.clients.get_mut(&client_id).unwrap();
|
||||
|
||||
// Always receive one be u32 of size, then the command.
|
||||
let mut size_bytes = [0u8; 4];
|
||||
client.stream.read_exact(&mut size_bytes)?;
|
||||
let size = u32::from_be_bytes(size_bytes);
|
||||
let mut bytes = vec![];
|
||||
bytes.resize(size as usize, 0u8);
|
||||
client
|
||||
.stream
|
||||
.read_exact(&mut bytes)
|
||||
.expect("Failed to read message body");
|
||||
let request: AshmemRequest = postcard::from_bytes(&bytes)?;
|
||||
|
||||
Ok(request)
|
||||
}
|
||||
fn handle_client(&mut self, client_id: RawFd) -> Result<(), Error> {
|
||||
let response = self.handle_request(client_id)?;
|
||||
|
||||
match response {
|
||||
AshmemResponse::Mapping(mapping) => {
|
||||
let id = mapping.borrow().id();
|
||||
let server_fd: i32 = id.to_string().parse().unwrap();
|
||||
let client = self.clients.get_mut(&client_id).unwrap();
|
||||
client
|
||||
.stream
|
||||
.send_fds(&id.to_string().as_bytes(), &[server_fd])?;
|
||||
client.maps.entry(server_fd).or_default().push(mapping);
|
||||
}
|
||||
AshmemResponse::Id(id) => {
|
||||
let client = self.clients.get_mut(&client_id).unwrap();
|
||||
client.stream.send_fds(&id.to_string().as_bytes(), &[])?;
|
||||
}
|
||||
AshmemResponse::RefCount(refcount) => {
|
||||
let client = self.clients.get_mut(&client_id).unwrap();
|
||||
client
|
||||
.stream
|
||||
.send_fds(&refcount.to_string().as_bytes(), &[])?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a new AshmemService, then listen and service incoming connections in a new thread.
|
||||
pub fn start() -> Result<thread::JoinHandle<Result<(), Error>>, Error> {
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
let syncpair = Arc::new((Mutex::new(false), Condvar::new()));
|
||||
let childsyncpair = Arc::clone(&syncpair);
|
||||
let join_handle =
|
||||
thread::spawn(move || Self::new()?.listen(ASHMEM_SERVER_NAME, &childsyncpair));
|
||||
|
||||
let (lock, cvar) = &*syncpair;
|
||||
let mut started = lock.lock().unwrap();
|
||||
while !*started {
|
||||
started = cvar.wait(started).unwrap();
|
||||
}
|
||||
|
||||
Ok(join_handle)
|
||||
}
|
||||
|
||||
/// Listen on a filename (or abstract name) for new connections and serve them. This function
|
||||
/// should not return.
|
||||
fn listen(
|
||||
&mut self,
|
||||
filename: &str,
|
||||
syncpair: &Arc<(Mutex<bool>, Condvar)>,
|
||||
) -> Result<(), Error> {
|
||||
let listener = if let Ok(listener) =
|
||||
UnixListener::bind_unix_addr(&UnixSocketAddr::new(filename)?)
|
||||
{
|
||||
listener
|
||||
} else {
|
||||
let (lock, cvar) = &**syncpair;
|
||||
*lock.lock().unwrap() = true;
|
||||
cvar.notify_one();
|
||||
return Err(Error::Unknown(
|
||||
"The server appears to already be running. We are probably a client".to_string(),
|
||||
));
|
||||
};
|
||||
let mut poll_fds: Vec<PollFd> = vec![PollFd::new(
|
||||
listener.as_raw_fd(),
|
||||
PollFlags::POLLIN | PollFlags::POLLRDNORM | PollFlags::POLLRDBAND,
|
||||
)];
|
||||
|
||||
let (lock, cvar) = &**syncpair;
|
||||
*lock.lock().unwrap() = true;
|
||||
cvar.notify_one();
|
||||
|
||||
loop {
|
||||
match poll(&mut poll_fds, -1) {
|
||||
Ok(num_fds) if num_fds > 0 => (),
|
||||
Ok(_) => continue,
|
||||
Err(e) => {
|
||||
println!("Error polling for activity: {:?}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let copied_poll_fds: Vec<PollFd> = poll_fds.iter().copied().collect();
|
||||
for poll_fd in copied_poll_fds {
|
||||
let revents = poll_fd.revents().expect("revents should not be None");
|
||||
let raw_polled_fd =
|
||||
unsafe { *((&poll_fd as *const PollFd) as *const libc::pollfd) }.fd;
|
||||
if revents.contains(PollFlags::POLLHUP) {
|
||||
poll_fds.remove(poll_fds.iter().position(|item| *item == poll_fd).unwrap());
|
||||
self.clients.remove(&raw_polled_fd);
|
||||
} else if revents.contains(PollFlags::POLLIN) {
|
||||
if self.clients.contains_key(&raw_polled_fd) {
|
||||
match self.handle_client(raw_polled_fd) {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
dbg!("Ignoring failed read from client", e, poll_fd);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
let (stream, addr) = match listener.accept_unix_addr() {
|
||||
Ok(stream_val) => stream_val,
|
||||
Err(e) => {
|
||||
println!("Error accepting client: {:?}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
println!("Recieved connection from {:?}", addr);
|
||||
let pollfd = PollFd::new(
|
||||
stream.as_raw_fd(),
|
||||
PollFlags::POLLIN | PollFlags::POLLRDNORM | PollFlags::POLLRDBAND,
|
||||
);
|
||||
poll_fds.push(pollfd);
|
||||
let client = AshmemClient::new(stream);
|
||||
let client_id = client.stream.as_raw_fd();
|
||||
self.clients.insert(client_id, client);
|
||||
match self.handle_client(client_id) {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
dbg!("Ignoring failed read from client", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
//println!("Unknown revents flags: {:?}", revents);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,8 @@
|
||||
//! Operating System specific abstractions
|
||||
|
||||
#[cfg(all(unix, feature = "std"))]
|
||||
pub mod ashmem_server;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub mod unix_signals;
|
||||
#[cfg(windows)]
|
||||
|
@ -1,3 +1,4 @@
|
||||
//! Signal handling for unix
|
||||
use alloc::vec::Vec;
|
||||
use core::{
|
||||
cell::UnsafeCell,
|
||||
@ -12,9 +13,9 @@ use core::{
|
||||
use std::ffi::CString;
|
||||
|
||||
use libc::{
|
||||
c_int, malloc, sigaction, sigaltstack, sigemptyset, stack_t, SA_NODEFER, SA_ONSTACK,
|
||||
SA_SIGINFO, SIGABRT, SIGALRM, SIGBUS, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGKILL, SIGPIPE,
|
||||
SIGQUIT, SIGSEGV, SIGTERM, SIGUSR2,
|
||||
c_int, malloc, sigaction, sigaltstack, sigemptyset, stack_t, ucontext_t, SA_NODEFER,
|
||||
SA_ONSTACK, SA_SIGINFO, SIGABRT, SIGALRM, SIGBUS, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGKILL,
|
||||
SIGPIPE, SIGQUIT, SIGSEGV, SIGTERM, SIGTRAP, SIGUSR2,
|
||||
};
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
|
||||
@ -24,6 +25,7 @@ pub use libc::{c_void, siginfo_t};
|
||||
|
||||
#[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy)]
|
||||
#[repr(i32)]
|
||||
#[allow(clippy::clippy::pub_enum_variant_names)]
|
||||
pub enum Signal {
|
||||
SigAbort = SIGABRT,
|
||||
SigBus = SIGBUS,
|
||||
@ -38,6 +40,7 @@ pub enum Signal {
|
||||
SigQuit = SIGQUIT,
|
||||
SigTerm = SIGTERM,
|
||||
SigInterrupt = SIGINT,
|
||||
SigTrap = SIGTRAP,
|
||||
}
|
||||
|
||||
pub static CRASH_SIGNALS: &[Signal] = &[
|
||||
@ -75,6 +78,7 @@ impl Display for Signal {
|
||||
Signal::SigQuit => write!(f, "SIGQUIT")?,
|
||||
Signal::SigTerm => write!(f, "SIGTERM")?,
|
||||
Signal::SigInterrupt => write!(f, "SIGINT")?,
|
||||
Signal::SigTrap => write!(f, "SIGTRAP")?,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
@ -83,7 +87,7 @@ impl Display for Signal {
|
||||
|
||||
pub trait Handler {
|
||||
/// Handle a signal
|
||||
fn handle(&mut self, signal: Signal, info: siginfo_t, _void: c_void);
|
||||
fn handle(&mut self, signal: Signal, info: siginfo_t, _context: &mut ucontext_t);
|
||||
/// Return a list of signals to handle
|
||||
fn signals(&self) -> Vec<Signal>;
|
||||
}
|
||||
@ -111,7 +115,7 @@ static mut SIGNAL_HANDLERS: [Option<HandlerHolder>; 32] = [
|
||||
/// # Safety
|
||||
/// This should be somewhat safe to call for signals previously registered,
|
||||
/// unless the signal handlers registered using [setup_signal_handler] are broken.
|
||||
unsafe fn handle_signal(sig: c_int, info: siginfo_t, void: c_void) {
|
||||
unsafe fn handle_signal(sig: c_int, info: siginfo_t, void: *mut c_void) {
|
||||
let signal = &Signal::try_from(sig).unwrap();
|
||||
let handler = {
|
||||
match &SIGNAL_HANDLERS[*signal as usize] {
|
||||
@ -119,7 +123,7 @@ unsafe fn handle_signal(sig: c_int, info: siginfo_t, void: c_void) {
|
||||
None => return,
|
||||
}
|
||||
};
|
||||
handler.handle(*signal, info, void);
|
||||
handler.handle(*signal, info, &mut *(void as *mut ucontext_t));
|
||||
}
|
||||
|
||||
/// Setup signal handlers in a somewhat rusty way.
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! Exception handling for Windows
|
||||
|
||||
pub use crate::bolts::bindings::windows::win32::debug::EXCEPTION_POINTERS;
|
||||
|
||||
use crate::{bolts::bindings::windows::win32::debug::SetUnhandledExceptionFilter, Error};
|
||||
@ -7,6 +9,7 @@ use core::{
|
||||
cell::UnsafeCell,
|
||||
convert::TryFrom,
|
||||
fmt::{self, Display, Formatter},
|
||||
ptr,
|
||||
ptr::write_volatile,
|
||||
sync::atomic::{compiler_fence, Ordering},
|
||||
};
|
||||
@ -14,10 +17,21 @@ use std::os::raw::{c_long, c_void};
|
||||
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
|
||||
const EXCEPTION_CONTINUE_EXECUTION: c_long = -1;
|
||||
//const EXCEPTION_CONTINUE_EXECUTION: c_long = -1;
|
||||
//const EXCEPTION_CONTINUE_SEARCH: c_long = 0;
|
||||
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;
|
||||
pub const SIGILL: i32 = 4;
|
||||
pub const SIGABRT_COMPAT: i32 = 6;
|
||||
pub const SIGFPE: i32 = 8;
|
||||
pub const SIGSEGV: i32 = 11;
|
||||
pub const SIGTERM: i32 = 15;
|
||||
pub const SIGBREAK: i32 = 21;
|
||||
pub const SIGABRT: i32 = 22;
|
||||
pub const SIGABRT2: i32 = 22;
|
||||
|
||||
// From https://github.com/wine-mirror/wine/blob/master/include/winnt.h#L611
|
||||
pub const STATUS_WAIT_0: u32 = 0x00000000;
|
||||
pub const STATUS_ABANDONED_WAIT_0: u32 = 0x00000080;
|
||||
@ -274,6 +288,24 @@ static mut EXCEPTION_HANDLERS: [Option<HandlerHolder>; 64] = [
|
||||
None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
|
||||
];
|
||||
|
||||
unsafe fn internal_handle_exception(
|
||||
exception_code: ExceptionCode,
|
||||
exception_pointers: *mut EXCEPTION_POINTERS,
|
||||
) -> i32 {
|
||||
let index = EXCEPTION_CODES_MAPPING
|
||||
.iter()
|
||||
.position(|x| *x == exception_code)
|
||||
.unwrap();
|
||||
match &EXCEPTION_HANDLERS[index] {
|
||||
Some(handler_holder) => {
|
||||
let handler = &mut **handler_holder.handler.get();
|
||||
handler.handle(exception_code, exception_pointers);
|
||||
EXCEPTION_EXECUTE_HANDLER
|
||||
}
|
||||
None => EXCEPTION_EXECUTE_HANDLER,
|
||||
}
|
||||
}
|
||||
|
||||
type NativeHandlerType = extern "system" fn(*mut EXCEPTION_POINTERS) -> c_long;
|
||||
static mut PREVIOUS_HANDLER: Option<NativeHandlerType> = None;
|
||||
|
||||
@ -287,18 +319,8 @@ unsafe extern "system" fn handle_exception(exception_pointers: *mut EXCEPTION_PO
|
||||
.unwrap()
|
||||
.exception_code;
|
||||
let exception_code = ExceptionCode::try_from(code).unwrap();
|
||||
let index = EXCEPTION_CODES_MAPPING
|
||||
.iter()
|
||||
.position(|x| *x == exception_code)
|
||||
.unwrap();
|
||||
let ret = match &EXCEPTION_HANDLERS[index] {
|
||||
Some(handler_holder) => {
|
||||
let handler = &mut **handler_holder.handler.get();
|
||||
handler.handle(exception_code, exception_pointers);
|
||||
EXCEPTION_EXECUTE_HANDLER
|
||||
}
|
||||
None => EXCEPTION_EXECUTE_HANDLER,
|
||||
};
|
||||
// println!("Received {}", exception_code);
|
||||
let ret = internal_handle_exception(exception_code, exception_pointers);
|
||||
if let Some(prev_handler) = PREVIOUS_HANDLER {
|
||||
prev_handler(exception_pointers)
|
||||
} else {
|
||||
@ -306,10 +328,26 @@ unsafe extern "system" fn handle_exception(exception_pointers: *mut EXCEPTION_PO
|
||||
}
|
||||
}
|
||||
|
||||
type NativeSignalHandlerType = unsafe extern "C" fn(i32);
|
||||
extern "C" {
|
||||
fn signal(signum: i32, func: NativeSignalHandlerType) -> *const c_void;
|
||||
}
|
||||
|
||||
unsafe extern "C" fn handle_signal(_signum: i32) {
|
||||
// println!("Received signal {}", _signum);
|
||||
internal_handle_exception(ExceptionCode::AssertionFailure, ptr::null_mut());
|
||||
}
|
||||
|
||||
/// Setup Win32 exception handlers in a somewhat rusty way.
|
||||
/// # Safety
|
||||
/// Exception handlers are usually ugly, handle with care!
|
||||
pub unsafe fn setup_exception_handler<T: 'static + Handler>(handler: &mut T) -> Result<(), Error> {
|
||||
let exceptions = handler.exceptions();
|
||||
let mut catch_assertions = false;
|
||||
for exception_code in exceptions {
|
||||
if exception_code == ExceptionCode::AssertionFailure {
|
||||
catch_assertions = true;
|
||||
}
|
||||
let index = EXCEPTION_CODES_MAPPING
|
||||
.iter()
|
||||
.position(|x| *x == exception_code)
|
||||
@ -322,7 +360,9 @@ pub unsafe fn setup_exception_handler<T: 'static + Handler>(handler: &mut T) ->
|
||||
);
|
||||
}
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
if catch_assertions {
|
||||
signal(SIGABRT, handle_signal);
|
||||
}
|
||||
if let Some(prev) = SetUnhandledExceptionFilter(Some(core::mem::transmute(
|
||||
handle_exception as *const c_void,
|
||||
))) {
|
||||
|
@ -5,66 +5,102 @@ use alloc::{boxed::Box, vec::Vec};
|
||||
use core::{clone::Clone, fmt::Debug};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
/// Trait to convert into an Owned type
|
||||
pub trait IntoOwned {
|
||||
fn is_owned(&self) -> bool;
|
||||
|
||||
fn into_owned(self) -> Self;
|
||||
}
|
||||
|
||||
/// Wrap a reference and convert to a Box on serialize
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Ptr<'a, T: 'a + ?Sized> {
|
||||
pub enum OwnedRef<'a, T>
|
||||
where
|
||||
T: 'a + ?Sized,
|
||||
{
|
||||
Ref(&'a T),
|
||||
Owned(Box<T>),
|
||||
}
|
||||
|
||||
impl<'a, T: 'a + ?Sized + Serialize> Serialize for Ptr<'a, T> {
|
||||
impl<'a, T> Serialize for OwnedRef<'a, T>
|
||||
where
|
||||
T: 'a + ?Sized + Serialize,
|
||||
{
|
||||
fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
Ptr::Ref(r) => r.serialize(se),
|
||||
Ptr::Owned(b) => b.serialize(se),
|
||||
OwnedRef::Ref(r) => r.serialize(se),
|
||||
OwnedRef::Owned(b) => b.serialize(se),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, 'a, T: 'a + ?Sized> Deserialize<'de> for Ptr<'a, T>
|
||||
impl<'de, 'a, T> Deserialize<'de> for OwnedRef<'a, T>
|
||||
where
|
||||
T: 'a + ?Sized,
|
||||
Box<T>: Deserialize<'de>,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Deserialize::deserialize(deserializer).map(Ptr::Owned)
|
||||
Deserialize::deserialize(deserializer).map(OwnedRef::Owned)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Sized> AsRef<T> for Ptr<'a, T> {
|
||||
impl<'a, T> AsRef<T> for OwnedRef<'a, T>
|
||||
where
|
||||
T: Sized,
|
||||
{
|
||||
fn as_ref(&self) -> &T {
|
||||
match self {
|
||||
Ptr::Ref(r) => r,
|
||||
Ptr::Owned(v) => v.as_ref(),
|
||||
OwnedRef::Ref(r) => r,
|
||||
OwnedRef::Owned(v) => v.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> IntoOwned for OwnedRef<'a, T>
|
||||
where
|
||||
T: Sized + Clone,
|
||||
{
|
||||
fn is_owned(&self) -> bool {
|
||||
match self {
|
||||
OwnedRef::Ref(_) => false,
|
||||
OwnedRef::Owned(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self {
|
||||
match self {
|
||||
OwnedRef::Ref(r) => OwnedRef::Owned(Box::new(r.clone())),
|
||||
OwnedRef::Owned(v) => OwnedRef::Owned(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap a mutable reference and convert to a Box on serialize
|
||||
#[derive(Debug)]
|
||||
pub enum PtrMut<'a, T: 'a + ?Sized> {
|
||||
pub enum OwnedRefMut<'a, T: 'a + ?Sized> {
|
||||
Ref(&'a mut T),
|
||||
Owned(Box<T>),
|
||||
}
|
||||
|
||||
impl<'a, T: 'a + ?Sized + Serialize> Serialize for PtrMut<'a, T> {
|
||||
impl<'a, T: 'a + ?Sized + Serialize> Serialize for OwnedRefMut<'a, T> {
|
||||
fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
PtrMut::Ref(r) => r.serialize(se),
|
||||
PtrMut::Owned(b) => b.serialize(se),
|
||||
OwnedRefMut::Ref(r) => r.serialize(se),
|
||||
OwnedRefMut::Owned(b) => b.serialize(se),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, 'a, T: 'a + ?Sized> Deserialize<'de> for PtrMut<'a, T>
|
||||
impl<'de, 'a, T: 'a + ?Sized> Deserialize<'de> for OwnedRefMut<'a, T>
|
||||
where
|
||||
Box<T>: Deserialize<'de>,
|
||||
{
|
||||
@ -72,48 +108,67 @@ where
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Deserialize::deserialize(deserializer).map(PtrMut::Owned)
|
||||
Deserialize::deserialize(deserializer).map(OwnedRefMut::Owned)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Sized> AsRef<T> for PtrMut<'a, T> {
|
||||
impl<'a, T: Sized> AsRef<T> for OwnedRefMut<'a, T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
match self {
|
||||
PtrMut::Ref(r) => r,
|
||||
PtrMut::Owned(v) => v.as_ref(),
|
||||
OwnedRefMut::Ref(r) => r,
|
||||
OwnedRefMut::Owned(v) => v.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Sized> AsMut<T> for PtrMut<'a, T> {
|
||||
impl<'a, T: Sized> AsMut<T> for OwnedRefMut<'a, T> {
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
match self {
|
||||
PtrMut::Ref(r) => r,
|
||||
PtrMut::Owned(v) => v.as_mut(),
|
||||
OwnedRefMut::Ref(r) => r,
|
||||
OwnedRefMut::Owned(v) => v.as_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> IntoOwned for OwnedRefMut<'a, T>
|
||||
where
|
||||
T: Sized + Clone,
|
||||
{
|
||||
fn is_owned(&self) -> bool {
|
||||
match self {
|
||||
OwnedRefMut::Ref(_) => false,
|
||||
OwnedRefMut::Owned(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self {
|
||||
match self {
|
||||
OwnedRefMut::Ref(r) => OwnedRefMut::Owned(Box::new(r.clone())),
|
||||
OwnedRefMut::Owned(v) => OwnedRefMut::Owned(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap a slice and convert to a Vec on serialize
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Slice<'a, T: 'a + Sized> {
|
||||
pub enum OwnedSlice<'a, T: 'a + Sized> {
|
||||
Ref(&'a [T]),
|
||||
Owned(Vec<T>),
|
||||
}
|
||||
|
||||
impl<'a, T: 'a + Sized + Serialize> Serialize for Slice<'a, T> {
|
||||
impl<'a, T: 'a + Sized + Serialize> Serialize for OwnedSlice<'a, T> {
|
||||
fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
Slice::Ref(r) => r.serialize(se),
|
||||
Slice::Owned(b) => b.serialize(se),
|
||||
OwnedSlice::Ref(r) => r.serialize(se),
|
||||
OwnedSlice::Owned(b) => b.serialize(se),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, 'a, T: 'a + Sized> Deserialize<'de> for Slice<'a, T>
|
||||
impl<'de, 'a, T: 'a + Sized> Deserialize<'de> for OwnedSlice<'a, T>
|
||||
where
|
||||
Vec<T>: Deserialize<'de>,
|
||||
{
|
||||
@ -121,39 +176,58 @@ where
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Deserialize::deserialize(deserializer).map(Slice::Owned)
|
||||
Deserialize::deserialize(deserializer).map(OwnedSlice::Owned)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Sized> Slice<'a, T> {
|
||||
impl<'a, T: Sized> OwnedSlice<'a, T> {
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
match self {
|
||||
Slice::Ref(r) => r,
|
||||
Slice::Owned(v) => v.as_slice(),
|
||||
OwnedSlice::Ref(r) => r,
|
||||
OwnedSlice::Owned(v) => v.as_slice(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> IntoOwned for OwnedSlice<'a, T>
|
||||
where
|
||||
T: Sized + Clone,
|
||||
{
|
||||
fn is_owned(&self) -> bool {
|
||||
match self {
|
||||
OwnedSlice::Ref(_) => false,
|
||||
OwnedSlice::Owned(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self {
|
||||
match self {
|
||||
OwnedSlice::Ref(r) => OwnedSlice::Owned(r.to_vec()),
|
||||
OwnedSlice::Owned(v) => OwnedSlice::Owned(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap a mutable slice and convert to a Vec on serialize
|
||||
#[derive(Debug)]
|
||||
pub enum SliceMut<'a, T: 'a + Sized> {
|
||||
pub enum OwnedSliceMut<'a, T: 'a + Sized> {
|
||||
Ref(&'a mut [T]),
|
||||
Owned(Vec<T>),
|
||||
}
|
||||
|
||||
impl<'a, T: 'a + Sized + Serialize> Serialize for SliceMut<'a, T> {
|
||||
impl<'a, T: 'a + Sized + Serialize> Serialize for OwnedSliceMut<'a, T> {
|
||||
fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
SliceMut::Ref(r) => r.serialize(se),
|
||||
SliceMut::Owned(b) => b.serialize(se),
|
||||
OwnedSliceMut::Ref(r) => r.serialize(se),
|
||||
OwnedSliceMut::Owned(b) => b.serialize(se),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, 'a, T: 'a + Sized> Deserialize<'de> for SliceMut<'a, T>
|
||||
impl<'de, 'a, T: 'a + Sized> Deserialize<'de> for OwnedSliceMut<'a, T>
|
||||
where
|
||||
Vec<T>: Deserialize<'de>,
|
||||
{
|
||||
@ -161,34 +235,53 @@ where
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Deserialize::deserialize(deserializer).map(SliceMut::Owned)
|
||||
Deserialize::deserialize(deserializer).map(OwnedSliceMut::Owned)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Sized> SliceMut<'a, T> {
|
||||
impl<'a, T: Sized> OwnedSliceMut<'a, T> {
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
match self {
|
||||
SliceMut::Ref(r) => r,
|
||||
SliceMut::Owned(v) => v.as_slice(),
|
||||
OwnedSliceMut::Ref(r) => r,
|
||||
OwnedSliceMut::Owned(v) => v.as_slice(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_mut_slice(&mut self) -> &[T] {
|
||||
pub fn as_mut_slice(&mut self) -> &mut [T] {
|
||||
match self {
|
||||
SliceMut::Ref(r) => r,
|
||||
SliceMut::Owned(v) => v.as_mut_slice(),
|
||||
OwnedSliceMut::Ref(r) => r,
|
||||
OwnedSliceMut::Owned(v) => v.as_mut_slice(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> IntoOwned for OwnedSliceMut<'a, T>
|
||||
where
|
||||
T: Sized + Clone,
|
||||
{
|
||||
fn is_owned(&self) -> bool {
|
||||
match self {
|
||||
OwnedSliceMut::Ref(_) => false,
|
||||
OwnedSliceMut::Owned(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self {
|
||||
match self {
|
||||
OwnedSliceMut::Ref(r) => OwnedSliceMut::Owned(r.to_vec()),
|
||||
OwnedSliceMut::Owned(v) => OwnedSliceMut::Owned(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap a C-style pointer and convert to a Box on serialize
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Cptr<T: Sized> {
|
||||
Cptr(*const T),
|
||||
pub enum OwnedPtr<T: Sized> {
|
||||
Ptr(*const T),
|
||||
Owned(Box<T>),
|
||||
}
|
||||
|
||||
impl<T: Sized + Serialize> Serialize for Cptr<T> {
|
||||
impl<T: Sized + Serialize> Serialize for OwnedPtr<T> {
|
||||
fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
@ -197,7 +290,7 @@ impl<T: Sized + Serialize> Serialize for Cptr<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T: Sized + serde::de::DeserializeOwned> Deserialize<'de> for Cptr<T>
|
||||
impl<'de, T: Sized + serde::de::DeserializeOwned> Deserialize<'de> for OwnedPtr<T>
|
||||
where
|
||||
Vec<T>: Deserialize<'de>,
|
||||
{
|
||||
@ -205,27 +298,46 @@ where
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Deserialize::deserialize(de).map(Cptr::Owned)
|
||||
Deserialize::deserialize(de).map(OwnedPtr::Owned)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Sized> AsRef<T> for Cptr<T> {
|
||||
impl<T: Sized> AsRef<T> for OwnedPtr<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
match self {
|
||||
Cptr::Cptr(p) => unsafe { p.as_ref().unwrap() },
|
||||
Cptr::Owned(v) => v.as_ref(),
|
||||
OwnedPtr::Ptr(p) => unsafe { p.as_ref().unwrap() },
|
||||
OwnedPtr::Owned(v) => v.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoOwned for OwnedPtr<T>
|
||||
where
|
||||
T: Sized + Clone,
|
||||
{
|
||||
fn is_owned(&self) -> bool {
|
||||
match self {
|
||||
OwnedPtr::Ptr(_) => false,
|
||||
OwnedPtr::Owned(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self {
|
||||
match self {
|
||||
OwnedPtr::Ptr(p) => unsafe { OwnedPtr::Owned(Box::new(p.as_ref().unwrap().clone())) },
|
||||
OwnedPtr::Owned(v) => OwnedPtr::Owned(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap a C-style mutable pointer and convert to a Box on serialize
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CptrMut<T: Sized> {
|
||||
Cptr(*mut T),
|
||||
pub enum OwnedPtrMut<T: Sized> {
|
||||
Ptr(*mut T),
|
||||
Owned(Box<T>),
|
||||
}
|
||||
|
||||
impl<T: Sized + Serialize> Serialize for CptrMut<T> {
|
||||
impl<T: Sized + Serialize> Serialize for OwnedPtrMut<T> {
|
||||
fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
@ -234,7 +346,7 @@ impl<T: Sized + Serialize> Serialize for CptrMut<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T: Sized + serde::de::DeserializeOwned> Deserialize<'de> for CptrMut<T>
|
||||
impl<'de, T: Sized + serde::de::DeserializeOwned> Deserialize<'de> for OwnedPtrMut<T>
|
||||
where
|
||||
Vec<T>: Deserialize<'de>,
|
||||
{
|
||||
@ -242,36 +354,57 @@ where
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Deserialize::deserialize(de).map(CptrMut::Owned)
|
||||
Deserialize::deserialize(de).map(OwnedPtrMut::Owned)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Sized> AsRef<T> for CptrMut<T> {
|
||||
impl<T: Sized> AsRef<T> for OwnedPtrMut<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
match self {
|
||||
CptrMut::Cptr(p) => unsafe { p.as_ref().unwrap() },
|
||||
CptrMut::Owned(b) => b.as_ref(),
|
||||
OwnedPtrMut::Ptr(p) => unsafe { p.as_ref().unwrap() },
|
||||
OwnedPtrMut::Owned(b) => b.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Sized> AsMut<T> for CptrMut<T> {
|
||||
impl<T: Sized> AsMut<T> for OwnedPtrMut<T> {
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
match self {
|
||||
CptrMut::Cptr(p) => unsafe { p.as_mut().unwrap() },
|
||||
CptrMut::Owned(b) => b.as_mut(),
|
||||
OwnedPtrMut::Ptr(p) => unsafe { p.as_mut().unwrap() },
|
||||
OwnedPtrMut::Owned(b) => b.as_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoOwned for OwnedPtrMut<T>
|
||||
where
|
||||
T: Sized + Clone,
|
||||
{
|
||||
fn is_owned(&self) -> bool {
|
||||
match self {
|
||||
OwnedPtrMut::Ptr(_) => false,
|
||||
OwnedPtrMut::Owned(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self {
|
||||
match self {
|
||||
OwnedPtrMut::Ptr(p) => unsafe {
|
||||
OwnedPtrMut::Owned(Box::new(p.as_ref().unwrap().clone()))
|
||||
},
|
||||
OwnedPtrMut::Owned(v) => OwnedPtrMut::Owned(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap a C-style pointer to an array (with size= and convert to a Vec on serialize
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Array<T: Sized> {
|
||||
Cptr((*const T, usize)),
|
||||
pub enum OwnedArrayPtr<T: Sized> {
|
||||
ArrayPtr((*const T, usize)),
|
||||
Owned(Vec<T>),
|
||||
}
|
||||
|
||||
impl<T: Sized + Serialize> Serialize for Array<T> {
|
||||
impl<T: Sized + Serialize> Serialize for OwnedArrayPtr<T> {
|
||||
fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
@ -280,7 +413,7 @@ impl<T: Sized + Serialize> Serialize for Array<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T: Sized + Serialize> Deserialize<'de> for Array<T>
|
||||
impl<'de, T: Sized + Serialize> Deserialize<'de> for OwnedArrayPtr<T>
|
||||
where
|
||||
Vec<T>: Deserialize<'de>,
|
||||
{
|
||||
@ -288,27 +421,48 @@ where
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Deserialize::deserialize(de).map(Array::Owned)
|
||||
Deserialize::deserialize(de).map(OwnedArrayPtr::Owned)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Sized> Array<T> {
|
||||
impl<T: Sized> OwnedArrayPtr<T> {
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
match self {
|
||||
Array::Cptr(p) => unsafe { core::slice::from_raw_parts(p.0, p.1) },
|
||||
Array::Owned(v) => v.as_slice(),
|
||||
OwnedArrayPtr::ArrayPtr(p) => unsafe { core::slice::from_raw_parts(p.0, p.1) },
|
||||
OwnedArrayPtr::Owned(v) => v.as_slice(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoOwned for OwnedArrayPtr<T>
|
||||
where
|
||||
T: Sized + Clone,
|
||||
{
|
||||
fn is_owned(&self) -> bool {
|
||||
match self {
|
||||
OwnedArrayPtr::ArrayPtr(_) => false,
|
||||
OwnedArrayPtr::Owned(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self {
|
||||
match self {
|
||||
OwnedArrayPtr::ArrayPtr(p) => unsafe {
|
||||
OwnedArrayPtr::Owned(core::slice::from_raw_parts(p.0, p.1).to_vec())
|
||||
},
|
||||
OwnedArrayPtr::Owned(v) => OwnedArrayPtr::Owned(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap a C-style mutable pointer to an array (with size= and convert to a Vec on serialize
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ArrayMut<T: Sized> {
|
||||
Cptr((*mut T, usize)),
|
||||
pub enum OwnedArrayPtrMut<T: Sized> {
|
||||
ArrayPtr((*mut T, usize)),
|
||||
Owned(Vec<T>),
|
||||
}
|
||||
|
||||
impl<T: Sized + Serialize> Serialize for ArrayMut<T> {
|
||||
impl<T: Sized + Serialize> Serialize for OwnedArrayPtrMut<T> {
|
||||
fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
@ -317,7 +471,7 @@ impl<T: Sized + Serialize> Serialize for ArrayMut<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T: Sized + Serialize> Deserialize<'de> for ArrayMut<T>
|
||||
impl<'de, T: Sized + Serialize> Deserialize<'de> for OwnedArrayPtrMut<T>
|
||||
where
|
||||
Vec<T>: Deserialize<'de>,
|
||||
{
|
||||
@ -325,22 +479,43 @@ where
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Deserialize::deserialize(de).map(ArrayMut::Owned)
|
||||
Deserialize::deserialize(de).map(OwnedArrayPtrMut::Owned)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Sized> ArrayMut<T> {
|
||||
impl<T: Sized> OwnedArrayPtrMut<T> {
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
match self {
|
||||
ArrayMut::Cptr(p) => unsafe { core::slice::from_raw_parts(p.0, p.1) },
|
||||
ArrayMut::Owned(v) => v.as_slice(),
|
||||
OwnedArrayPtrMut::ArrayPtr(p) => unsafe { core::slice::from_raw_parts(p.0, p.1) },
|
||||
OwnedArrayPtrMut::Owned(v) => v.as_slice(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_mut_slice(&mut self) -> &mut [T] {
|
||||
match self {
|
||||
ArrayMut::Cptr(p) => unsafe { core::slice::from_raw_parts_mut(p.0, p.1) },
|
||||
ArrayMut::Owned(v) => v.as_mut_slice(),
|
||||
OwnedArrayPtrMut::ArrayPtr(p) => unsafe { core::slice::from_raw_parts_mut(p.0, p.1) },
|
||||
OwnedArrayPtrMut::Owned(v) => v.as_mut_slice(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoOwned for OwnedArrayPtrMut<T>
|
||||
where
|
||||
T: Sized + Clone,
|
||||
{
|
||||
fn is_owned(&self) -> bool {
|
||||
match self {
|
||||
OwnedArrayPtrMut::ArrayPtr(_) => false,
|
||||
OwnedArrayPtrMut::Owned(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self {
|
||||
match self {
|
||||
OwnedArrayPtrMut::ArrayPtr(p) => unsafe {
|
||||
OwnedArrayPtrMut::Owned(core::slice::from_raw_parts(p.0, p.1).to_vec())
|
||||
},
|
||||
OwnedArrayPtrMut::Owned(v) => OwnedArrayPtrMut::Owned(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,6 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use alloc::boxed::Box;
|
||||
use core::any::{Any, TypeId};
|
||||
|
||||
#[cfg(feature = "anymap_debug")]
|
||||
use serde_json;
|
||||
|
||||
// yolo
|
||||
|
||||
pub fn pack_type_id(id: u64) -> TypeId {
|
||||
@ -181,15 +178,15 @@ macro_rules! create_serde_registry_for_trait {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(fature = "anymapdbg")]
|
||||
#[cfg(feature = "anymap_debug")]
|
||||
impl fmt::Debug for SerdeAnyMap {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
let json = serde_json::to_string(&self);
|
||||
write!(f, "SerdeAnyMap: [{}]", json)
|
||||
write!(f, "SerdeAnyMap: [{:?}]", json)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(fature = "anymapdbg"))]
|
||||
#[cfg(not(feature = "anymap_debug"))]
|
||||
impl fmt::Debug for SerdeAnyMap {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "SerdeAnymap with {} elements", self.len())
|
||||
@ -277,7 +274,7 @@ macro_rules! create_serde_registry_for_trait {
|
||||
None => None,
|
||||
Some(h) => h
|
||||
.get(&xxhash_rust::xxh3::xxh3_64(name.as_bytes()))
|
||||
.map(|x| x.as_ref()),
|
||||
.map(AsRef::as_ref),
|
||||
}
|
||||
}
|
||||
|
||||
@ -304,7 +301,7 @@ macro_rules! create_serde_registry_for_trait {
|
||||
None => None,
|
||||
Some(h) => h
|
||||
.get_mut(&xxhash_rust::xxh3::xxh3_64(name.as_bytes()))
|
||||
.map(|x| x.as_mut()),
|
||||
.map(AsMut::as_mut),
|
||||
}
|
||||
}
|
||||
|
||||
@ -496,10 +493,11 @@ macro_rules! create_serde_registry_for_trait {
|
||||
create_serde_registry_for_trait!(serdeany_registry, crate::bolts::serdeany::SerdeAny);
|
||||
pub use serdeany_registry::*;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[macro_export]
|
||||
macro_rules! impl_serdeany {
|
||||
($struct_name:ident) => {
|
||||
impl crate::bolts::serdeany::SerdeAny for $struct_name {
|
||||
impl $crate::bolts::serdeany::SerdeAny for $struct_name {
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
@ -510,10 +508,25 @@ macro_rules! impl_serdeany {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[cfg(feature = "std")]
|
||||
#[ctor]
|
||||
#[$crate::ctor]
|
||||
fn $struct_name() {
|
||||
crate::bolts::serdeany::RegistryBuilder::register::<$struct_name>();
|
||||
$crate::bolts::serdeany::RegistryBuilder::register::<$struct_name>();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[macro_export]
|
||||
macro_rules! impl_serdeany {
|
||||
($struct_name:ident) => {
|
||||
impl $crate::bolts::serdeany::SerdeAny for $struct_name {
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -4,7 +4,11 @@ pub use tuple_list::{tuple_list, tuple_list_type, TupleList};
|
||||
|
||||
use core::any::TypeId;
|
||||
|
||||
use xxhash_rust::const_xxh3::xxh3_64;
|
||||
|
||||
pub trait HasLen {
|
||||
const LEN: usize;
|
||||
|
||||
fn len(&self) -> usize;
|
||||
fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
@ -12,6 +16,8 @@ pub trait HasLen {
|
||||
}
|
||||
|
||||
impl HasLen for () {
|
||||
const LEN: usize = 0;
|
||||
|
||||
fn len(&self) -> usize {
|
||||
0
|
||||
}
|
||||
@ -19,13 +25,61 @@ impl HasLen for () {
|
||||
|
||||
impl<Head, Tail> HasLen for (Head, Tail)
|
||||
where
|
||||
Tail: TupleList + HasLen,
|
||||
Tail: HasLen,
|
||||
{
|
||||
const LEN: usize = 1 + Tail::LEN;
|
||||
|
||||
fn len(&self) -> usize {
|
||||
1 + self.1.len()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasNameId {
|
||||
fn const_name(&self) -> &'static str;
|
||||
|
||||
fn name_id(&self) -> u64 {
|
||||
xxh3_64(self.const_name().as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasNameIdTuple: HasLen {
|
||||
fn get_const_name(&self, index: usize) -> Option<&'static str>;
|
||||
|
||||
fn get_name_id(&self, index: usize) -> Option<u64>;
|
||||
}
|
||||
|
||||
impl HasNameIdTuple for () {
|
||||
fn get_const_name(&self, _index: usize) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn get_name_id(&self, _index: usize) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<Head, Tail> HasNameIdTuple for (Head, Tail)
|
||||
where
|
||||
Head: 'static + HasNameId,
|
||||
Tail: HasNameIdTuple,
|
||||
{
|
||||
fn get_const_name(&self, index: usize) -> Option<&'static str> {
|
||||
if index == 0 {
|
||||
Some(self.0.const_name())
|
||||
} else {
|
||||
self.1.get_const_name(index - 1)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_name_id(&self, index: usize) -> Option<u64> {
|
||||
if index == 0 {
|
||||
Some(self.0.name_id())
|
||||
} else {
|
||||
self.1.get_name_id(index - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MatchFirstType {
|
||||
fn match_first_type<T: 'static>(&self) -> Option<&T>;
|
||||
fn match_first_type_mut<T: 'static>(&mut self) -> Option<&mut T>;
|
||||
@ -43,7 +97,7 @@ impl MatchFirstType for () {
|
||||
impl<Head, Tail> MatchFirstType for (Head, Tail)
|
||||
where
|
||||
Head: 'static,
|
||||
Tail: TupleList + MatchFirstType,
|
||||
Tail: MatchFirstType,
|
||||
{
|
||||
fn match_first_type<T: 'static>(&self) -> Option<&T> {
|
||||
if TypeId::of::<T>() == TypeId::of::<Head>() {
|
||||
@ -75,7 +129,7 @@ impl MatchType for () {
|
||||
impl<Head, Tail> MatchType for (Head, Tail)
|
||||
where
|
||||
Head: 'static,
|
||||
Tail: TupleList + MatchType,
|
||||
Tail: MatchType,
|
||||
{
|
||||
fn match_type<T: 'static>(&self, f: fn(t: &T)) {
|
||||
if TypeId::of::<T>() == TypeId::of::<Head>() {
|
||||
@ -98,6 +152,30 @@ pub trait Named {
|
||||
fn name(&self) -> &str;
|
||||
}
|
||||
|
||||
pub trait NamedTuple: HasLen {
|
||||
fn get_name(&self, index: usize) -> Option<&str>;
|
||||
}
|
||||
|
||||
impl NamedTuple for () {
|
||||
fn get_name(&self, _index: usize) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<Head, Tail> NamedTuple for (Head, Tail)
|
||||
where
|
||||
Head: 'static + Named,
|
||||
Tail: NamedTuple,
|
||||
{
|
||||
fn get_name(&self, index: usize) -> Option<&str> {
|
||||
if index == 0 {
|
||||
Some(self.0.name())
|
||||
} else {
|
||||
self.1.get_name(index - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MatchNameAndType {
|
||||
fn match_name_type<T: 'static>(&self, name: &str) -> Option<&T>;
|
||||
fn match_name_type_mut<T: 'static>(&mut self, name: &str) -> Option<&mut T>;
|
||||
@ -115,7 +193,7 @@ impl MatchNameAndType for () {
|
||||
impl<Head, Tail> MatchNameAndType for (Head, Tail)
|
||||
where
|
||||
Head: 'static + Named,
|
||||
Tail: TupleList + MatchNameAndType,
|
||||
Tail: MatchNameAndType,
|
||||
{
|
||||
fn match_name_type<T: 'static>(&self, name: &str) -> Option<&T> {
|
||||
if TypeId::of::<T>() == TypeId::of::<Head>() && name == self.0.name() {
|
||||
|
@ -150,6 +150,8 @@ where
|
||||
C: Corpus<I>,
|
||||
R: Rand,
|
||||
{
|
||||
/// Update the `Corpus` score using the `MinimizerCorpusScheduler`
|
||||
#[allow(clippy::unused_self)]
|
||||
pub fn update_score(&self, state: &mut S, idx: usize) -> Result<(), Error> {
|
||||
// Create a new top rated meta if not existing
|
||||
if state.metadata().get::<TopRatedsMetadata>().is_none() {
|
||||
@ -194,16 +196,18 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cull the `Corpus` using the `MinimizerCorpusScheduler`
|
||||
#[allow(clippy::unused_self)]
|
||||
pub fn cull(&self, state: &mut S) -> Result<(), Error> {
|
||||
if state.metadata().get::<TopRatedsMetadata>().is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut acc = HashSet::new();
|
||||
let top_rated = state.metadata().get::<TopRatedsMetadata>().unwrap();
|
||||
let top_rated = match state.metadata().get::<TopRatedsMetadata>() {
|
||||
None => return Ok(()),
|
||||
Some(val) => val,
|
||||
};
|
||||
|
||||
for key in top_rated.map.keys() {
|
||||
let mut acc = HashSet::new();
|
||||
|
||||
for (key, idx) in &top_rated.map {
|
||||
if !acc.contains(key) {
|
||||
let idx = top_rated.map.get(key).unwrap();
|
||||
let mut entry = state.corpus().get(*idx)?.borrow_mut();
|
||||
let meta = entry.metadata().get::<M>().ok_or_else(|| {
|
||||
Error::KeyNotFound(format!(
|
||||
|
@ -5,9 +5,21 @@ use core::cell::RefCell;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use std::{fs, path::PathBuf};
|
||||
use std::{fs, fs::File, io::Write, path::PathBuf};
|
||||
|
||||
use crate::{corpus::Corpus, corpus::Testcase, inputs::Input, Error};
|
||||
use crate::{corpus::Corpus, corpus::Testcase, inputs::Input, state::HasMetadata, Error};
|
||||
|
||||
/// Options for the the format of the on-disk metadata
|
||||
#[cfg(feature = "std")]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum OnDiskMetadataFormat {
|
||||
/// A binary-encoded postcard
|
||||
Postcard,
|
||||
/// JSON
|
||||
Json,
|
||||
/// JSON formatted for readability
|
||||
JsonPretty,
|
||||
}
|
||||
|
||||
/// A corpus able to store testcases to disk, and load them from disk, when they are being used.
|
||||
#[cfg(feature = "std")]
|
||||
@ -20,6 +32,7 @@ where
|
||||
entries: Vec<RefCell<Testcase<I>>>,
|
||||
current: Option<usize>,
|
||||
dir_path: PathBuf,
|
||||
meta_format: Option<OnDiskMetadataFormat>,
|
||||
}
|
||||
|
||||
impl<I> Corpus<I> for OnDiskCorpus<I>
|
||||
@ -41,6 +54,17 @@ where
|
||||
let filename_str = filename.to_str().expect("Invalid Path");
|
||||
testcase.set_filename(filename_str.into());
|
||||
};
|
||||
if self.meta_format.is_some() {
|
||||
let filename = testcase.filename().as_ref().unwrap().to_owned() + ".metadata";
|
||||
let mut file = File::create(filename)?;
|
||||
|
||||
let serialized = match self.meta_format.as_ref().unwrap() {
|
||||
OnDiskMetadataFormat::Postcard => postcard::to_allocvec(testcase.metadata())?,
|
||||
OnDiskMetadataFormat::Json => serde_json::to_vec(testcase.metadata())?,
|
||||
OnDiskMetadataFormat::JsonPretty => serde_json::to_vec_pretty(testcase.metadata())?,
|
||||
};
|
||||
file.write_all(&serialized)?;
|
||||
}
|
||||
testcase
|
||||
.store_input()
|
||||
.expect("Could not save testcase to disk");
|
||||
@ -99,6 +123,22 @@ where
|
||||
entries: vec![],
|
||||
current: None,
|
||||
dir_path,
|
||||
meta_format: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates the OnDiskCorpus specifying the type of metatada to be saved to disk.
|
||||
/// Will error, if `std::fs::create_dir_all` failed for `dir_path`.
|
||||
pub fn new_save_meta(
|
||||
dir_path: PathBuf,
|
||||
meta_format: Option<OnDiskMetadataFormat>,
|
||||
) -> Result<Self, Error> {
|
||||
fs::create_dir_all(&dir_path)?;
|
||||
Ok(Self {
|
||||
entries: vec![],
|
||||
current: None,
|
||||
dir_path,
|
||||
meta_format,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -217,14 +217,15 @@ where
|
||||
self.cached_len = Some(l);
|
||||
l
|
||||
}
|
||||
None => match self.cached_len {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
None => {
|
||||
if let Some(l) = self.cached_len {
|
||||
l
|
||||
} else {
|
||||
let l = self.load_input()?.len();
|
||||
self.cached_len = Some(l);
|
||||
l
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! LLMP-backed event manager for scalable multi-processed fuzzing
|
||||
|
||||
use alloc::{string::ToString, vec::Vec};
|
||||
use core::{marker::PhantomData, time::Duration};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
@ -6,20 +8,15 @@ use serde::{de::DeserializeOwned, Serialize};
|
||||
use core::ptr::read_volatile;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use crate::bolts::llmp::LlmpReceiver;
|
||||
use crate::bolts::{
|
||||
llmp::{LlmpClient, LlmpReceiver},
|
||||
shmem::StdShMemProvider,
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "std", windows))]
|
||||
use crate::utils::startable_self;
|
||||
|
||||
#[cfg(all(feature = "std", unix))]
|
||||
use crate::utils::{fork, ForkResult};
|
||||
|
||||
#[cfg(all(feature = "std", unix))]
|
||||
use crate::bolts::shmem::UnixShMem;
|
||||
use crate::{
|
||||
bolts::{
|
||||
llmp::{self, LlmpClient, LlmpClientDescription, LlmpSender, Tag},
|
||||
shmem::{HasFd, ShMem},
|
||||
llmp::{self, Flag, LlmpClientDescription, LlmpSender, Tag},
|
||||
shmem::ShMemProvider,
|
||||
},
|
||||
corpus::CorpusScheduler,
|
||||
events::{BrokerEventResult, Event, EventManager},
|
||||
@ -32,6 +29,21 @@ use crate::{
|
||||
Error,
|
||||
};
|
||||
|
||||
#[cfg(feature = "llmp_compression")]
|
||||
use crate::bolts::{
|
||||
compress::GzipCompressor,
|
||||
llmp::{LLMP_FLAG_COMPRESSED, LLMP_FLAG_INITIALIZED},
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "std", windows))]
|
||||
use crate::utils::startable_self;
|
||||
|
||||
#[cfg(all(feature = "std", unix))]
|
||||
use crate::utils::{fork, ForkResult};
|
||||
|
||||
#[cfg(all(feature = "std", target_os = "android"))]
|
||||
use crate::bolts::os::ashmem_server::AshmemService;
|
||||
|
||||
/// Forward this to the client
|
||||
const _LLMP_TAG_EVENT_TO_CLIENT: llmp::Tag = 0x2C11E471;
|
||||
/// Only handle this in the broker
|
||||
@ -39,57 +51,35 @@ const _LLMP_TAG_EVENT_TO_BROKER: llmp::Tag = 0x2B80438;
|
||||
/// Handle in both
|
||||
///
|
||||
const LLMP_TAG_EVENT_TO_BOTH: llmp::Tag = 0x2B0741;
|
||||
|
||||
const _LLMP_TAG_RESTART: llmp::Tag = 0x8357A87;
|
||||
const _LLMP_TAG_NO_RESTART: llmp::Tag = 0x57A7EE71;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LlmpEventManager<I, S, SH, ST>
|
||||
#[derive(Debug)]
|
||||
pub struct LlmpEventManager<I, S, SP, ST>
|
||||
where
|
||||
I: Input,
|
||||
S: IfInteresting<I>,
|
||||
SH: ShMem,
|
||||
SP: ShMemProvider + 'static,
|
||||
ST: Stats,
|
||||
//CE: CustomEvent<I>,
|
||||
{
|
||||
stats: Option<ST>,
|
||||
llmp: llmp::LlmpConnection<SH>,
|
||||
llmp: llmp::LlmpConnection<SP>,
|
||||
#[cfg(feature = "llmp_compression")]
|
||||
compressor: GzipCompressor,
|
||||
|
||||
phantom: PhantomData<(I, S)>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[cfg(unix)]
|
||||
impl<I, S, ST> LlmpEventManager<I, S, UnixShMem, ST>
|
||||
/// The minimum buffer size at which to compress LLMP IPC messages.
|
||||
#[cfg(feature = "llmp_compression")]
|
||||
const COMPRESS_THRESHOLD: usize = 1024;
|
||||
|
||||
impl<I, S, SP, ST> Drop for LlmpEventManager<I, S, SP, ST>
|
||||
where
|
||||
I: Input,
|
||||
S: IfInteresting<I>,
|
||||
ST: Stats,
|
||||
{
|
||||
/// Create llmp on a port
|
||||
/// If the port is not yet bound, it will act as broker
|
||||
/// Else, it will act as client.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn new_on_port_std(stats: ST, port: u16) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
stats: Some(stats),
|
||||
llmp: llmp::LlmpConnection::on_port(port)?,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// If a client respawns, it may reuse the existing connection, previously stored by LlmpClient::to_env
|
||||
/// Std uses UnixShMem.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn existing_client_from_env_std(env_name: &str) -> Result<Self, Error> {
|
||||
Self::existing_client_from_env(env_name)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S, SH, ST> Drop for LlmpEventManager<I, S, SH, ST>
|
||||
where
|
||||
I: Input,
|
||||
S: IfInteresting<I>,
|
||||
SH: ShMem,
|
||||
SP: ShMemProvider,
|
||||
ST: Stats,
|
||||
{
|
||||
/// LLMP clients will have to wait until their pages are mapped by somebody.
|
||||
@ -98,33 +88,37 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S, SH, ST> LlmpEventManager<I, S, SH, ST>
|
||||
impl<I, S, SP, ST> LlmpEventManager<I, S, SP, ST>
|
||||
where
|
||||
I: Input,
|
||||
S: IfInteresting<I>,
|
||||
SH: ShMem,
|
||||
SP: ShMemProvider,
|
||||
ST: Stats,
|
||||
{
|
||||
/// Create llmp on a port
|
||||
/// If the port is not yet bound, it will act as broker
|
||||
/// Else, it will act as client.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn new_on_port(stats: ST, port: u16) -> Result<Self, Error> {
|
||||
pub fn new_on_port(shmem_provider: SP, stats: ST, port: u16) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
stats: Some(stats),
|
||||
llmp: llmp::LlmpConnection::on_port(port)?,
|
||||
llmp: llmp::LlmpConnection::on_port(shmem_provider, port)?,
|
||||
#[cfg(feature = "llmp_compression")]
|
||||
compressor: GzipCompressor::new(COMPRESS_THRESHOLD),
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// If a client respawns, it may reuse the existing connection, previously stored by LlmpClient::to_env
|
||||
#[cfg(feature = "std")]
|
||||
pub fn existing_client_from_env(env_name: &str) -> Result<Self, Error> {
|
||||
pub fn existing_client_from_env(shmem_provider: SP, env_name: &str) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
stats: None,
|
||||
llmp: llmp::LlmpConnection::IsClient {
|
||||
client: LlmpClient::on_existing_from_env(env_name)?,
|
||||
client: LlmpClient::on_existing_from_env(shmem_provider, env_name)?,
|
||||
},
|
||||
#[cfg(feature = "llmp_compression")]
|
||||
compressor: GzipCompressor::new(COMPRESS_THRESHOLD),
|
||||
// Inserting a nop-stats element here so rust won't complain.
|
||||
// In any case, the client won't currently use it.
|
||||
phantom: PhantomData,
|
||||
@ -138,26 +132,23 @@ where
|
||||
|
||||
/// Create an existing client from description
|
||||
pub fn existing_client_from_description(
|
||||
shmem_provider: SP,
|
||||
description: &LlmpClientDescription,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
stats: None,
|
||||
llmp: llmp::LlmpConnection::existing_client_from_description(description)?,
|
||||
llmp: llmp::LlmpConnection::existing_client_from_description(
|
||||
shmem_provider,
|
||||
description,
|
||||
)?,
|
||||
#[cfg(feature = "llmp_compression")]
|
||||
compressor: GzipCompressor::new(COMPRESS_THRESHOLD),
|
||||
// Inserting a nop-stats element here so rust won't complain.
|
||||
// In any case, the client won't currently use it.
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// A client on an existing map
|
||||
pub fn for_client(client: LlmpClient<SH>) -> Self {
|
||||
Self {
|
||||
stats: None,
|
||||
llmp: llmp::LlmpConnection::IsClient { client },
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the config for a client eventmgr to env vars, a new client can reattach using existing_client_from_env
|
||||
#[cfg(feature = "std")]
|
||||
pub fn to_env(&self, env_name: &str) {
|
||||
@ -179,10 +170,24 @@ where
|
||||
match &mut self.llmp {
|
||||
llmp::LlmpConnection::IsBroker { broker } => {
|
||||
let stats = self.stats.as_mut().unwrap();
|
||||
#[cfg(feature = "llmp_compression")]
|
||||
let compressor = &self.compressor;
|
||||
broker.loop_forever(
|
||||
&mut |sender_id: u32, tag: Tag, msg: &[u8]| {
|
||||
&mut |sender_id: u32, tag: Tag, _flags: Flag, msg: &[u8]| {
|
||||
if tag == LLMP_TAG_EVENT_TO_BOTH {
|
||||
let event: Event<I> = postcard::from_bytes(msg)?;
|
||||
#[cfg(not(feature = "llmp_compression"))]
|
||||
let event_bytes = msg;
|
||||
#[cfg(feature = "llmp_compression")]
|
||||
let compressed;
|
||||
#[cfg(feature = "llmp_compression")]
|
||||
let event_bytes =
|
||||
if _flags & LLMP_FLAG_COMPRESSED == LLMP_FLAG_COMPRESSED {
|
||||
compressed = compressor.decompress(msg)?;
|
||||
&compressed
|
||||
} else {
|
||||
msg
|
||||
};
|
||||
let event: Event<I> = postcard::from_bytes(event_bytes)?;
|
||||
match Self::handle_in_broker(stats, sender_id, &event)? {
|
||||
BrokerEventResult::Forward => {
|
||||
Ok(llmp::LlmpMsgHookResult::ForwardToClients)
|
||||
@ -205,6 +210,7 @@ where
|
||||
}
|
||||
|
||||
/// Handle arriving events in the broker
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn handle_in_broker(
|
||||
stats: &mut ST,
|
||||
sender_id: u32,
|
||||
@ -256,6 +262,7 @@ where
|
||||
}
|
||||
|
||||
// Handle arriving events in the client
|
||||
#[allow(clippy::unused_self)]
|
||||
fn handle_in_client<CS, E, OT>(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
@ -285,7 +292,7 @@ where
|
||||
|
||||
let observers: OT = postcard::from_bytes(&observers_buf)?;
|
||||
// TODO include ExitKind in NewTestcase
|
||||
let fitness = state.is_interesting(&input, &observers, ExitKind::Ok)?;
|
||||
let fitness = state.is_interesting(&input, &observers, &ExitKind::Ok)?;
|
||||
if fitness > 0
|
||||
&& state
|
||||
.add_if_interesting(&input, fitness, scheduler)?
|
||||
@ -304,28 +311,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S, SH, ST> LlmpEventManager<I, S, SH, ST>
|
||||
impl<I, S, SP, ST> EventManager<I, S> for LlmpEventManager<I, S, SP, ST>
|
||||
where
|
||||
I: Input,
|
||||
S: IfInteresting<I>,
|
||||
SH: ShMem + HasFd,
|
||||
ST: Stats,
|
||||
{
|
||||
#[cfg(all(feature = "std", unix))]
|
||||
pub fn new_on_domain_socket(stats: ST, filename: &str) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
stats: Some(stats),
|
||||
llmp: llmp::LlmpConnection::on_domain_socket(filename)?,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S, SH, ST> EventManager<I, S> for LlmpEventManager<I, S, SH, ST>
|
||||
where
|
||||
I: Input,
|
||||
S: IfInteresting<I>,
|
||||
SH: ShMem,
|
||||
SP: ShMemProvider,
|
||||
ST: Stats, //CE: CustomEvent<I>,
|
||||
{
|
||||
/// The llmp client needs to wait until a broker mapped all pages, before shutting down.
|
||||
@ -352,11 +342,22 @@ where
|
||||
let mut events = vec![];
|
||||
match &mut self.llmp {
|
||||
llmp::LlmpConnection::IsClient { client } => {
|
||||
while let Some((sender_id, tag, msg)) = client.recv_buf()? {
|
||||
while let Some((sender_id, tag, _flags, msg)) = client.recv_buf_with_flags()? {
|
||||
if tag == _LLMP_TAG_EVENT_TO_BROKER {
|
||||
panic!("EVENT_TO_BROKER parcel should not have arrived in the client!");
|
||||
}
|
||||
let event: Event<I> = postcard::from_bytes(msg)?;
|
||||
#[cfg(not(feature = "llmp_compression"))]
|
||||
let event_bytes = msg;
|
||||
#[cfg(feature = "llmp_compression")]
|
||||
let compressed;
|
||||
#[cfg(feature = "llmp_compression")]
|
||||
let event_bytes = if _flags & LLMP_FLAG_COMPRESSED == LLMP_FLAG_COMPRESSED {
|
||||
compressed = self.compressor.decompress(msg)?;
|
||||
&compressed
|
||||
} else {
|
||||
msg
|
||||
};
|
||||
let event: Event<I> = postcard::from_bytes(event_bytes)?;
|
||||
events.push((sender_id, event));
|
||||
}
|
||||
}
|
||||
@ -372,6 +373,27 @@ where
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
#[cfg(feature = "llmp_compression")]
|
||||
fn fire(&mut self, _state: &mut S, event: Event<I>) -> Result<(), Error> {
|
||||
let serialized = postcard::to_allocvec(&event)?;
|
||||
let flags: Flag = LLMP_FLAG_INITIALIZED;
|
||||
|
||||
match self.compressor.compress(&serialized)? {
|
||||
Some(comp_buf) => {
|
||||
self.llmp.send_buf_with_flags(
|
||||
LLMP_TAG_EVENT_TO_BOTH,
|
||||
&comp_buf,
|
||||
flags | LLMP_FLAG_COMPRESSED,
|
||||
)?;
|
||||
}
|
||||
None => {
|
||||
self.llmp.send_buf(LLMP_TAG_EVENT_TO_BOTH, &serialized)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "llmp_compression"))]
|
||||
fn fire(&mut self, _state: &mut S, event: Event<I>) -> Result<(), Error> {
|
||||
let serialized = postcard::to_allocvec(&event)?;
|
||||
self.llmp.send_buf(LLMP_TAG_EVENT_TO_BOTH, &serialized)?;
|
||||
@ -382,57 +404,59 @@ where
|
||||
/// Serialize the current state and corpus during an executiont to bytes.
|
||||
/// On top, add the current llmp event manager instance to be restored
|
||||
/// This method is needed when the fuzzer run crashes and has to restart.
|
||||
pub fn serialize_state_mgr<I, S, SH, ST>(
|
||||
pub fn serialize_state_mgr<I, S, SP, ST>(
|
||||
state: &S,
|
||||
mgr: &LlmpEventManager<I, S, SH, ST>,
|
||||
mgr: &LlmpEventManager<I, S, SP, ST>,
|
||||
) -> Result<Vec<u8>, Error>
|
||||
where
|
||||
I: Input,
|
||||
S: Serialize + IfInteresting<I>,
|
||||
SH: ShMem,
|
||||
SP: ShMemProvider,
|
||||
ST: Stats,
|
||||
{
|
||||
Ok(postcard::to_allocvec(&(&state, &mgr.describe()?))?)
|
||||
}
|
||||
|
||||
/// Deserialize the state and corpus tuple, previously serialized with `serialize_state_corpus(...)`
|
||||
pub fn deserialize_state_mgr<I, S, SH, ST>(
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn deserialize_state_mgr<I, S, SP, ST>(
|
||||
shmem_provider: SP,
|
||||
state_corpus_serialized: &[u8],
|
||||
) -> Result<(S, LlmpEventManager<I, S, SH, ST>), Error>
|
||||
) -> Result<(S, LlmpEventManager<I, S, SP, ST>), Error>
|
||||
where
|
||||
I: Input,
|
||||
S: DeserializeOwned + IfInteresting<I>,
|
||||
SH: ShMem,
|
||||
SP: ShMemProvider,
|
||||
ST: Stats,
|
||||
{
|
||||
let tuple: (S, _) = postcard::from_bytes(&state_corpus_serialized)?;
|
||||
Ok((
|
||||
tuple.0,
|
||||
LlmpEventManager::existing_client_from_description(&tuple.1)?,
|
||||
LlmpEventManager::existing_client_from_description(shmem_provider, &tuple.1)?,
|
||||
))
|
||||
}
|
||||
|
||||
/// A manager that can restart on the fly, storing states in-between (in `on_resatrt`)
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LlmpRestartingEventManager<I, S, SH, ST>
|
||||
#[derive(Debug)]
|
||||
pub struct LlmpRestartingEventManager<I, S, SP, ST>
|
||||
where
|
||||
I: Input,
|
||||
S: IfInteresting<I>,
|
||||
SH: ShMem,
|
||||
SP: ShMemProvider + 'static,
|
||||
ST: Stats,
|
||||
//CE: CustomEvent<I>,
|
||||
{
|
||||
/// The embedded llmp event manager
|
||||
llmp_mgr: LlmpEventManager<I, S, SH, ST>,
|
||||
llmp_mgr: LlmpEventManager<I, S, SP, ST>,
|
||||
/// The sender to serialize the state for the next runner
|
||||
sender: LlmpSender<SH>,
|
||||
sender: LlmpSender<SP>,
|
||||
}
|
||||
|
||||
impl<I, S, SH, ST> EventManager<I, S> for LlmpRestartingEventManager<I, S, SH, ST>
|
||||
impl<I, S, SP, ST> EventManager<I, S> for LlmpRestartingEventManager<I, S, SP, ST>
|
||||
where
|
||||
I: Input,
|
||||
S: IfInteresting<I> + Serialize,
|
||||
SH: ShMem,
|
||||
SP: ShMemProvider,
|
||||
ST: Stats, //CE: CustomEvent<I>,
|
||||
{
|
||||
/// The llmp client needs to wait until a broker mapped all pages, before shutting down.
|
||||
@ -477,117 +501,157 @@ const _ENV_FUZZER_RECEIVER: &str = &"_AFL_ENV_FUZZER_RECEIVER";
|
||||
/// The llmp (2 way) connection from a fuzzer to the broker (broadcasting all other fuzzer messages)
|
||||
const _ENV_FUZZER_BROKER_CLIENT_INITIAL: &str = &"_AFL_ENV_FUZZER_BROKER_CLIENT";
|
||||
|
||||
impl<I, S, SH, ST> LlmpRestartingEventManager<I, S, SH, ST>
|
||||
impl<I, S, SP, ST> LlmpRestartingEventManager<I, S, SP, ST>
|
||||
where
|
||||
I: Input,
|
||||
S: IfInteresting<I>,
|
||||
SH: ShMem,
|
||||
SP: ShMemProvider,
|
||||
ST: Stats, //CE: CustomEvent<I>,
|
||||
{
|
||||
/// Create a new runner, the executed child doing the actual fuzzing.
|
||||
pub fn new(llmp_mgr: LlmpEventManager<I, S, SH, ST>, sender: LlmpSender<SH>) -> Self {
|
||||
pub fn new(llmp_mgr: LlmpEventManager<I, S, SP, ST>, sender: LlmpSender<SP>) -> Self {
|
||||
Self { llmp_mgr, sender }
|
||||
}
|
||||
|
||||
/// Get the sender
|
||||
pub fn sender(&self) -> &LlmpSender<SH> {
|
||||
pub fn sender(&self) -> &LlmpSender<SP> {
|
||||
&self.sender
|
||||
}
|
||||
|
||||
/// Get the sender (mut)
|
||||
pub fn sender_mut(&mut self) -> &mut LlmpSender<SH> {
|
||||
pub fn sender_mut(&mut self) -> &mut LlmpSender<SP> {
|
||||
&mut self.sender
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn setup_restarting_mgr_std<I, S, ST>(
|
||||
//mgr: &mut LlmpEventManager<I, S, SH, ST>,
|
||||
stats: ST,
|
||||
broker_port: u16,
|
||||
) -> Result<
|
||||
(
|
||||
Option<S>,
|
||||
LlmpRestartingEventManager<I, S, StdShMemProvider, ST>,
|
||||
),
|
||||
Error,
|
||||
>
|
||||
where
|
||||
I: Input,
|
||||
S: DeserializeOwned + IfInteresting<I>,
|
||||
ST: Stats,
|
||||
{
|
||||
#[cfg(target_os = "android")]
|
||||
AshmemService::start().expect("Error starting Ashmem Service");
|
||||
|
||||
setup_restarting_mgr(StdShMemProvider::new()?, stats, broker_port)
|
||||
}
|
||||
|
||||
/// A restarting state is a combination of restarter and runner, that can be used on systems without `fork`.
|
||||
/// The restarter will start a new process each time the child crashes or timeouts.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn setup_restarting_mgr<I, S, SH, ST>(
|
||||
#[allow(
|
||||
clippy::unnecessary_operation,
|
||||
clippy::type_complexity,
|
||||
clippy::similar_names
|
||||
)] // for { mgr = LlmpEventManager... }
|
||||
pub fn setup_restarting_mgr<I, S, SP, ST>(
|
||||
mut shmem_provider: SP,
|
||||
//mgr: &mut LlmpEventManager<I, S, SH, ST>,
|
||||
stats: ST,
|
||||
broker_port: u16,
|
||||
) -> Result<(Option<S>, LlmpRestartingEventManager<I, S, SH, ST>), Error>
|
||||
) -> Result<(Option<S>, LlmpRestartingEventManager<I, S, SP, ST>), Error>
|
||||
where
|
||||
I: Input,
|
||||
S: DeserializeOwned + IfInteresting<I>,
|
||||
SH: ShMem + HasFd, // Todo: HasFd is only needed for Android
|
||||
SP: ShMemProvider,
|
||||
ST: Stats,
|
||||
{
|
||||
let mut mgr;
|
||||
let mut mgr =
|
||||
LlmpEventManager::<I, S, SP, ST>::new_on_port(shmem_provider.clone(), stats, broker_port)?;
|
||||
|
||||
// We start ourself as child process to actually fuzz
|
||||
let (sender, mut receiver) = if std::env::var(_ENV_FUZZER_SENDER).is_err() {
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
let path = std::env::current_dir()?;
|
||||
mgr = LlmpEventManager::<I, S, SH, ST>::new_on_domain_socket(stats, "\x00llmp_socket")?;
|
||||
};
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{
|
||||
mgr = LlmpEventManager::<I, S, SH, ST>::new_on_port(stats, broker_port)?
|
||||
};
|
||||
|
||||
let (sender, mut receiver, mut new_shmem_provider) = if std::env::var(_ENV_FUZZER_SENDER)
|
||||
.is_err()
|
||||
{
|
||||
if mgr.is_broker() {
|
||||
// Yep, broker. Just loop here.
|
||||
println!("Doing broker things. Run this tool again to start fuzzing in a client.");
|
||||
mgr.broker_loop()?;
|
||||
return Err(Error::ShuttingDown);
|
||||
} else {
|
||||
// We are the fuzzer respawner in a llmp client
|
||||
mgr.to_env(_ENV_FUZZER_BROKER_CLIENT_INITIAL);
|
||||
}
|
||||
|
||||
// First, create a channel from the fuzzer (sender) to us (receiver) to report its state for restarts.
|
||||
let sender = LlmpSender::new(0, false)?;
|
||||
let receiver = LlmpReceiver::on_existing_map(
|
||||
SH::clone_ref(&sender.out_maps.last().unwrap().shmem)?,
|
||||
None,
|
||||
)?;
|
||||
// Store the information to a map.
|
||||
sender.to_env(_ENV_FUZZER_SENDER)?;
|
||||
receiver.to_env(_ENV_FUZZER_RECEIVER)?;
|
||||
// We are the fuzzer respawner in a llmp client
|
||||
mgr.to_env(_ENV_FUZZER_BROKER_CLIENT_INITIAL);
|
||||
|
||||
let mut ctr: u64 = 0;
|
||||
// Client->parent loop
|
||||
loop {
|
||||
dbg!("Spawning next client (id {})", ctr);
|
||||
// First, create a channel from the fuzzer (sender) to us (receiver) to report its state for restarts.
|
||||
let sender = { LlmpSender::new(shmem_provider.clone(), 0, false)? };
|
||||
|
||||
// On Unix, we fork (todo: measure if that is actually faster.)
|
||||
let map = { shmem_provider.clone_ref(&sender.out_maps.last().unwrap().shmem)? };
|
||||
let receiver = LlmpReceiver::on_existing_map(shmem_provider.clone(), map, None)?;
|
||||
// Store the information to a map.
|
||||
sender.to_env(_ENV_FUZZER_SENDER)?;
|
||||
receiver.to_env(_ENV_FUZZER_RECEIVER)?;
|
||||
|
||||
let mut ctr: u64 = 0;
|
||||
// Client->parent loop
|
||||
loop {
|
||||
dbg!("Spawning next client (id {})", ctr);
|
||||
|
||||
// On Unix, we fork (todo: measure if that is actually faster.)
|
||||
#[cfg(unix)]
|
||||
let child_status = match unsafe { fork() }? {
|
||||
ForkResult::Parent(handle) => handle.status(),
|
||||
ForkResult::Child => break (sender, receiver, shmem_provider),
|
||||
};
|
||||
|
||||
// On windows, we spawn ourself again
|
||||
#[cfg(windows)]
|
||||
let child_status = startable_self()?.status()?;
|
||||
|
||||
if unsafe { read_volatile(&(*receiver.current_recv_map.page()).size_used) } == 0 {
|
||||
#[cfg(unix)]
|
||||
let _ = match unsafe { fork() }? {
|
||||
ForkResult::Parent(handle) => handle.status(),
|
||||
ForkResult::Child => break (sender, receiver),
|
||||
};
|
||||
|
||||
// On windows, we spawn ourself again
|
||||
#[cfg(windows)]
|
||||
startable_self()?.status()?;
|
||||
|
||||
if unsafe { read_volatile(&(*receiver.current_recv_map.page()).size_used) } == 0 {
|
||||
// Storing state in the last round did not work
|
||||
panic!("Fuzzer-respawner: Storing state in crashed fuzzer instance did not work, no point to spawn the next client!");
|
||||
if child_status == 137 {
|
||||
// Out of Memory, see https://tldp.org/LDP/abs/html/exitcodes.html
|
||||
// and https://github.com/AFLplusplus/LibAFL/issues/32 for discussion.
|
||||
panic!("Fuzzer-respawner: The fuzzed target crashed with an out of memory error! Fix your harness, or switch to another executor (for example, a forkserver).");
|
||||
}
|
||||
|
||||
ctr = ctr.wrapping_add(1);
|
||||
// Storing state in the last round did not work
|
||||
panic!("Fuzzer-respawner: Storing state in crashed fuzzer instance did not work, no point to spawn the next client! (Child exited with: {})", child_status);
|
||||
}
|
||||
|
||||
ctr = ctr.wrapping_add(1);
|
||||
}
|
||||
} else {
|
||||
// We are the newly started fuzzing instance, first, connect to our own restore map.
|
||||
// A sender and a receiver for single communication
|
||||
// Clone so we get a new connection to the AshmemServer if we are using
|
||||
// ServedShMemProvider
|
||||
shmem_provider.post_fork();
|
||||
(
|
||||
LlmpSender::<SH>::on_existing_from_env(_ENV_FUZZER_SENDER)?,
|
||||
LlmpReceiver::<SH>::on_existing_from_env(_ENV_FUZZER_RECEIVER)?,
|
||||
LlmpSender::on_existing_from_env(shmem_provider.clone(), _ENV_FUZZER_SENDER)?,
|
||||
LlmpReceiver::on_existing_from_env(shmem_provider.clone(), _ENV_FUZZER_RECEIVER)?,
|
||||
shmem_provider,
|
||||
)
|
||||
};
|
||||
|
||||
new_shmem_provider.post_fork();
|
||||
|
||||
println!("We're a client, let's fuzz :)");
|
||||
|
||||
for (var, val) in std::env::vars() {
|
||||
println!("ENV VARS: {:?}: {:?}", var, val);
|
||||
}
|
||||
|
||||
// If we're restarting, deserialize the old state.
|
||||
let (state, mut mgr) = match receiver.recv_buf()? {
|
||||
None => {
|
||||
println!("First run. Let's set it all up");
|
||||
// Mgr to send and receive msgs from/to all other fuzzer instances
|
||||
let client_mgr = LlmpEventManager::<I, S, SH, ST>::existing_client_from_env(
|
||||
let client_mgr = LlmpEventManager::<I, S, SP, ST>::existing_client_from_env(
|
||||
new_shmem_provider,
|
||||
_ENV_FUZZER_BROKER_CLIENT_INITIAL,
|
||||
)?;
|
||||
|
||||
@ -596,7 +660,8 @@ where
|
||||
// Restoring from a previous run, deserialize state and corpus.
|
||||
Some((_sender, _tag, msg)) => {
|
||||
println!("Subsequent run. Let's load all data from shmem (received {} bytes from previous instance)", msg.len());
|
||||
let (state, mgr): (S, LlmpEventManager<I, S, SH, ST>) = deserialize_state_mgr(&msg)?;
|
||||
let (state, mgr): (S, LlmpEventManager<I, S, SP, ST>) =
|
||||
deserialize_state_mgr(new_shmem_provider, &msg)?;
|
||||
|
||||
(Some(state), LlmpRestartingEventManager::new(mgr, sender))
|
||||
}
|
||||
|
@ -246,7 +246,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_event_serde() {
|
||||
let obv = StdMapObserver::new("test", unsafe { &mut MAP });
|
||||
let obv = StdMapObserver::new("test", unsafe { &mut MAP }, unsafe { MAP.len() });
|
||||
let map = tuple_list!(obv);
|
||||
let observers_buf = postcard::to_allocvec(&map).unwrap();
|
||||
|
||||
|
@ -73,6 +73,7 @@ where
|
||||
}
|
||||
|
||||
// Handle arriving events in the broker
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn handle_in_broker(stats: &mut ST, event: &Event<I>) -> Result<BrokerEventResult, Error> {
|
||||
match event {
|
||||
Event::NewTestcase {
|
||||
@ -83,8 +84,12 @@ where
|
||||
time,
|
||||
executions,
|
||||
} => {
|
||||
stats.client_stats_mut()[0].update_corpus_size(*corpus_size as u64);
|
||||
stats.client_stats_mut()[0].update_executions(*executions as u64, *time);
|
||||
stats
|
||||
.client_stats_mut_for(0)
|
||||
.update_corpus_size(*corpus_size as u64);
|
||||
stats
|
||||
.client_stats_mut_for(0)
|
||||
.update_executions(*executions as u64, *time);
|
||||
stats.display(event.name().to_string());
|
||||
Ok(BrokerEventResult::Handled)
|
||||
}
|
||||
@ -94,12 +99,16 @@ where
|
||||
phantom: _,
|
||||
} => {
|
||||
// TODO: The stats buffer should be added on client add.
|
||||
stats.client_stats_mut()[0].update_executions(*executions as u64, *time);
|
||||
stats
|
||||
.client_stats_mut_for(0)
|
||||
.update_executions(*executions as u64, *time);
|
||||
stats.display(event.name().to_string());
|
||||
Ok(BrokerEventResult::Handled)
|
||||
}
|
||||
Event::Objective { objective_size } => {
|
||||
stats.client_stats_mut()[0].update_objective_size(*objective_size as u64);
|
||||
stats
|
||||
.client_stats_mut_for(0)
|
||||
.update_objective_size(*objective_size as u64);
|
||||
stats.display(event.name().to_string());
|
||||
Ok(BrokerEventResult::Handled)
|
||||
}
|
||||
@ -117,6 +126,7 @@ where
|
||||
}
|
||||
|
||||
// Handle arriving events in the client
|
||||
#[allow(clippy::needless_pass_by_value, clippy::unused_self)]
|
||||
fn handle_in_client(&mut self, _state: &mut S, event: Event<I>) -> Result<(), Error> {
|
||||
Err(Error::Unknown(format!(
|
||||
"Received illegal message that message should not have arrived: {:?}.",
|
||||
|
@ -25,26 +25,25 @@ use crate::{
|
||||
Error,
|
||||
};
|
||||
|
||||
/// The inmem executor harness
|
||||
type HarnessFunction<E> = fn(&E, &[u8]) -> ExitKind;
|
||||
|
||||
/// The inmem executor simply calls a target function, then returns afterwards.
|
||||
pub struct InProcessExecutor<I, OT>
|
||||
pub struct InProcessExecutor<'a, H, I, OT>
|
||||
where
|
||||
H: FnMut(&[u8]) -> ExitKind,
|
||||
I: Input + HasTargetBytes,
|
||||
OT: ObserversTuple,
|
||||
{
|
||||
/// The name of this executor instance, to address it from other components
|
||||
name: &'static str,
|
||||
/// The harness function, being executed for each fuzzing loop execution
|
||||
harness_fn: HarnessFunction<Self>,
|
||||
harness_fn: &'a mut H,
|
||||
/// The observers, observing each run
|
||||
observers: OT,
|
||||
phantom: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<I, OT> Executor<I> for InProcessExecutor<I, OT>
|
||||
impl<'a, H, I, OT> Executor<I> for InProcessExecutor<'a, H, I, OT>
|
||||
where
|
||||
H: FnMut(&[u8]) -> ExitKind,
|
||||
I: Input + HasTargetBytes,
|
||||
OT: ObserversTuple,
|
||||
{
|
||||
@ -95,7 +94,7 @@ where
|
||||
#[inline]
|
||||
fn run_target(&mut self, input: &I) -> Result<ExitKind, Error> {
|
||||
let bytes = input.target_bytes();
|
||||
let ret = (self.harness_fn)(self, bytes.as_slice());
|
||||
let ret = (self.harness_fn)(bytes.as_slice());
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
@ -126,8 +125,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, OT> Named for InProcessExecutor<I, OT>
|
||||
impl<'a, H, I, OT> Named for InProcessExecutor<'a, H, I, OT>
|
||||
where
|
||||
H: FnMut(&[u8]) -> ExitKind,
|
||||
I: Input + HasTargetBytes,
|
||||
OT: ObserversTuple,
|
||||
{
|
||||
@ -136,8 +136,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, OT> HasObservers<OT> for InProcessExecutor<I, OT>
|
||||
impl<'a, H, I, OT> HasObservers<OT> for InProcessExecutor<'a, H, I, OT>
|
||||
where
|
||||
H: FnMut(&[u8]) -> ExitKind,
|
||||
I: Input + HasTargetBytes,
|
||||
OT: ObserversTuple,
|
||||
{
|
||||
@ -152,8 +153,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, OT> InProcessExecutor<I, OT>
|
||||
impl<'a, H, I, OT> InProcessExecutor<'a, H, I, OT>
|
||||
where
|
||||
H: FnMut(&[u8]) -> ExitKind,
|
||||
I: Input + HasTargetBytes,
|
||||
OT: ObserversTuple,
|
||||
{
|
||||
@ -166,7 +168,7 @@ where
|
||||
/// This may return an error on unix, if signal handler setup fails
|
||||
pub fn new<EM, OC, OFT, S>(
|
||||
name: &'static str,
|
||||
harness_fn: HarnessFunction<Self>,
|
||||
harness_fn: &'a mut H,
|
||||
observers: OT,
|
||||
_state: &mut S,
|
||||
_event_mgr: &mut EM,
|
||||
@ -215,13 +217,25 @@ where
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Retrieve the harness function.
|
||||
#[inline]
|
||||
pub fn harness(&self) -> &H {
|
||||
self.harness_fn
|
||||
}
|
||||
|
||||
/// Retrieve the harness function for a mutable reference.
|
||||
#[inline]
|
||||
pub fn harness_mut(&mut self) -> &mut H {
|
||||
self.harness_fn
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
mod unix_signal_handler {
|
||||
use alloc::vec::Vec;
|
||||
use core::ptr;
|
||||
use libc::{c_void, siginfo_t};
|
||||
use libc::{c_void, siginfo_t, ucontext_t};
|
||||
#[cfg(feature = "std")]
|
||||
use std::io::{stdout, Write};
|
||||
|
||||
@ -259,8 +273,8 @@ mod unix_signal_handler {
|
||||
pub event_mgr_ptr: *mut c_void,
|
||||
pub observers_ptr: *const c_void,
|
||||
pub current_input_ptr: *const c_void,
|
||||
pub crash_handler: unsafe fn(Signal, siginfo_t, c_void, data: &mut Self),
|
||||
pub timeout_handler: unsafe fn(Signal, siginfo_t, c_void, data: &mut Self),
|
||||
pub crash_handler: unsafe fn(Signal, siginfo_t, &mut ucontext_t, data: &mut Self),
|
||||
pub timeout_handler: unsafe fn(Signal, siginfo_t, &mut ucontext_t, data: &mut Self),
|
||||
}
|
||||
|
||||
unsafe impl Send for InProcessExecutorHandlerData {}
|
||||
@ -269,21 +283,21 @@ mod unix_signal_handler {
|
||||
unsafe fn nop_handler(
|
||||
_signal: Signal,
|
||||
_info: siginfo_t,
|
||||
_void: c_void,
|
||||
_context: &mut ucontext_t,
|
||||
_data: &mut InProcessExecutorHandlerData,
|
||||
) {
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl Handler for InProcessExecutorHandlerData {
|
||||
fn handle(&mut self, signal: Signal, info: siginfo_t, void: c_void) {
|
||||
fn handle(&mut self, signal: Signal, info: siginfo_t, context: &mut ucontext_t) {
|
||||
unsafe {
|
||||
let data = &mut GLOBAL_STATE;
|
||||
match signal {
|
||||
Signal::SigUser2 | Signal::SigAlarm => {
|
||||
(data.timeout_handler)(signal, info, void, data)
|
||||
(data.timeout_handler)(signal, info, context, data)
|
||||
}
|
||||
_ => (data.crash_handler)(signal, info, void, data),
|
||||
_ => (data.crash_handler)(signal, info, context, data),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -298,6 +312,7 @@ mod unix_signal_handler {
|
||||
Signal::SigFloatingPointException,
|
||||
Signal::SigIllegalInstruction,
|
||||
Signal::SigSegmentationFault,
|
||||
Signal::SigTrap,
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -306,7 +321,7 @@ mod unix_signal_handler {
|
||||
pub unsafe fn inproc_timeout_handler<EM, I, OC, OFT, OT, S>(
|
||||
_signal: Signal,
|
||||
_info: siginfo_t,
|
||||
_void: c_void,
|
||||
_context: &mut ucontext_t,
|
||||
data: &mut InProcessExecutorHandlerData,
|
||||
) where
|
||||
EM: EventManager<I, S>,
|
||||
@ -334,7 +349,7 @@ mod unix_signal_handler {
|
||||
|
||||
let obj_fitness = state
|
||||
.objectives_mut()
|
||||
.is_interesting_all(&input, observers, ExitKind::Timeout)
|
||||
.is_interesting_all(&input, observers, &ExitKind::Timeout)
|
||||
.expect("In timeout handler objectives failure.");
|
||||
if obj_fitness > 0 {
|
||||
state
|
||||
@ -368,7 +383,7 @@ mod unix_signal_handler {
|
||||
pub unsafe fn inproc_crash_handler<EM, I, OC, OFT, OT, S>(
|
||||
_signal: Signal,
|
||||
_info: siginfo_t,
|
||||
_void: c_void,
|
||||
_context: &mut ucontext_t,
|
||||
data: &mut InProcessExecutorHandlerData,
|
||||
) where
|
||||
EM: EventManager<I, S>,
|
||||
@ -378,6 +393,10 @@ mod unix_signal_handler {
|
||||
S: HasObjectives<OFT, I> + HasSolutions<OC, I>,
|
||||
I: Input + HasTargetBytes,
|
||||
{
|
||||
#[cfg(all(target_os = "android", target_arch = "aarch64"))]
|
||||
let _context = *(((_context as *mut _ as *mut c_void as usize) + 128) as *mut c_void
|
||||
as *mut ucontext_t);
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
println!("Crashed with {}", _signal);
|
||||
if !data.current_input_ptr.is_null() {
|
||||
@ -387,6 +406,45 @@ mod unix_signal_handler {
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
println!("Child crashed!");
|
||||
|
||||
#[cfg(all(
|
||||
feature = "std",
|
||||
any(target_os = "linux", target_os = "android"),
|
||||
target_arch = "aarch64"
|
||||
))]
|
||||
{
|
||||
use crate::utils::find_mapping_for_address;
|
||||
println!("{:━^100}", " CRASH ");
|
||||
println!(
|
||||
"Received signal {} at 0x{:016x}, fault address: 0x{:016x}",
|
||||
_signal, _context.uc_mcontext.pc, _context.uc_mcontext.fault_address
|
||||
);
|
||||
if let Ok((start, _, _, path)) =
|
||||
find_mapping_for_address(_context.uc_mcontext.pc as usize)
|
||||
{
|
||||
println!(
|
||||
"pc is at offset 0x{:08x} in {}",
|
||||
_context.uc_mcontext.pc as usize - start,
|
||||
path
|
||||
);
|
||||
}
|
||||
|
||||
println!("{:━^100}", " REGISTERS ");
|
||||
for reg in 0..31 {
|
||||
print!(
|
||||
"x{:02}: 0x{:016x} ",
|
||||
reg, _context.uc_mcontext.regs[reg as usize]
|
||||
);
|
||||
if reg % 4 == 3 {
|
||||
println!();
|
||||
}
|
||||
}
|
||||
println!("pc : 0x{:016x} ", _context.uc_mcontext.pc);
|
||||
|
||||
//println!("{:━^100}", " BACKTRACE ");
|
||||
//println!("{:?}", backtrace::Backtrace::new())
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
let _ = stdout().flush();
|
||||
|
||||
@ -396,7 +454,7 @@ mod unix_signal_handler {
|
||||
|
||||
let obj_fitness = state
|
||||
.objectives_mut()
|
||||
.is_interesting_all(&input, observers, ExitKind::Crash)
|
||||
.is_interesting_all(&input, observers, &ExitKind::Crash)
|
||||
.expect("In crash handler objectives failure.");
|
||||
if obj_fitness > 0 {
|
||||
let new_input = input.clone();
|
||||
@ -560,7 +618,7 @@ mod windows_exception_handler {
|
||||
|
||||
let obj_fitness = state
|
||||
.objectives_mut()
|
||||
.is_interesting_all(&input, observers, ExitKind::Crash)
|
||||
.is_interesting_all(&input, observers, &ExitKind::Crash)
|
||||
.expect("In crash handler objectives failure.");
|
||||
if obj_fitness > 0 {
|
||||
let new_input = input.clone();
|
||||
@ -627,19 +685,15 @@ mod tests {
|
||||
use crate::{
|
||||
bolts::tuples::tuple_list,
|
||||
executors::{Executor, ExitKind, InProcessExecutor},
|
||||
inputs::Input,
|
||||
inputs::NopInput,
|
||||
};
|
||||
|
||||
fn test_harness_fn_nop<E: Executor<I>, I: Input>(_executor: &E, _buf: &[u8]) -> ExitKind {
|
||||
ExitKind::Ok
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inmem_exec() {
|
||||
use crate::inputs::NopInput;
|
||||
let mut harness = |_buf: &[u8]| ExitKind::Ok;
|
||||
|
||||
let mut in_process_executor = InProcessExecutor::<NopInput, ()> {
|
||||
harness_fn: test_harness_fn_nop,
|
||||
let mut in_process_executor = InProcessExecutor::<_, NopInput, ()> {
|
||||
harness_fn: &mut harness,
|
||||
observers: tuple_list!(),
|
||||
name: "main",
|
||||
phantom: PhantomData,
|
||||
|
@ -4,27 +4,29 @@ pub mod inprocess;
|
||||
pub use inprocess::InProcessExecutor;
|
||||
pub mod timeout;
|
||||
pub use timeout::TimeoutExecutor;
|
||||
#[cfg(feature = "runtime")]
|
||||
pub mod runtime;
|
||||
|
||||
use core::cmp::PartialEq;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use crate::{
|
||||
bolts::tuples::Named,
|
||||
bolts::{serdeany::SerdeAny, tuples::Named},
|
||||
events::EventManager,
|
||||
inputs::{HasTargetBytes, Input},
|
||||
observers::ObserversTuple,
|
||||
Error,
|
||||
};
|
||||
|
||||
use alloc::boxed::Box;
|
||||
|
||||
pub trait CustomExitKind: core::fmt::Debug + SerdeAny + 'static {}
|
||||
|
||||
/// How an execution finished.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub enum ExitKind {
|
||||
Ok,
|
||||
Crash,
|
||||
OOM,
|
||||
Oom,
|
||||
Timeout,
|
||||
Custom(Box<dyn CustomExitKind>),
|
||||
}
|
||||
|
||||
pub trait HasObservers<OT>
|
||||
@ -51,7 +53,7 @@ where
|
||||
}
|
||||
|
||||
/// A simple executor that does nothing.
|
||||
/// If intput len is 0, run_target will return Err
|
||||
/// If intput len is 0, `run_target` will return Err
|
||||
struct NopExecutor<I> {
|
||||
phantom: PhantomData<I>,
|
||||
}
|
||||
|