Merge pull request #61 from AFLplusplus/dev

Dev
This commit is contained in:
Andrea Fioraldi 2021-04-30 10:59:10 +02:00 committed by GitHub
commit 2864d62d6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
140 changed files with 18181 additions and 3413 deletions

View File

@ -2,7 +2,7 @@ name: Build and Test
on: on:
push: push:
branches: [ main ] branches: [ main, dev ]
pull_request: pull_request:
branches: [ main, dev ] branches: [ main, dev ]
@ -63,5 +63,11 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Windows Build - name: Windows Build
run: cargo build --verbose run: cargo build --verbose
- name: Windows Test # TODO: Figure out how to properly build stuff with clang
run: cargo test --verbose #- 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
View File

@ -18,3 +18,6 @@ perf.data.old
.vscode .vscode
test.dict test.dict
# Ignore all built fuzzers
fuzzer_*

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "fuzzers/qemufuzzer/qemu-fuzz"]
path = fuzzers/qemufuzzer/qemu-fuzz
url = git@github.com:AFLplusplus/qemu-fuzz.git

View File

@ -8,9 +8,16 @@ debug = true
members = [ members = [
"libafl", "libafl",
"libafl_derive", "libafl_derive",
"libafl_cc",
#example fuzzers "libafl_targets",
"fuzzers/libfuzzer_libpng", "libafl_frida",
"fuzzers/libfuzzer_libmozjpeg", ]
"fuzzers/libfuzzer_libpng_cmpalloc", default-members = [
"libafl",
"libafl_derive",
"libafl_cc",
"libafl_targets",
]
exclude = [
"fuzzers",
] ]

View File

@ -1,10 +1,41 @@
# LibAFL, the fuzzer library. # 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. 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>. 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 ## Getting started
@ -25,29 +56,51 @@ Build the library using
cargo build --release cargo build --release
``` ```
Build the documentation with Build the API documentation with
``` ```
cargo doc 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)
[![Video explaining libAFL's core concepts](http://img.youtube.com/vi/3RWkT1Q5IV0/3.jpg)](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 ## Contributing
Check the [TODO.md](./TODO.md) file for features that we plan to support. 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 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
View File

@ -1,22 +1,26 @@
# TODOs # TODOs
- [x] ~~Minset corpus scheduler~~ still doc missing - [ ] Conditional composition of feedbacks (issue #24)
- [ ] Win32 shared mem and crash handler to have Windows in-process executor
- [x] Other feedbacks examples (e.g. maximize allocations to spot OOMs)
- [ ] Other objectives examples (e.g. execution of a given program point) - [ ] Other objectives examples (e.g. execution of a given program point)
- [ ] Objective-Specific Corpuses (named per objective) - [ ] Objective-Specific Corpuses (named per objective)
- [x] A macro crate with derive directives (e.g. for SerdeAny impl).
- [ ] Good documentation - [ ] Good documentation
- [ ] LLMP brotli compression - [ ] LLMP compression
- [ ] AFL-Style Forkserver Executor - [ ] AFL-Style Forkserver Executor
- [x] Restarting EventMgr could use forks on unix
- [ ] Android Ashmem support
- [ ] Restart Count in Fuzzing Loop - [ ] Restart Count in Fuzzing Loop
- [ ] LAIN / structured fuzzing example - [ ] LAIN / structured fuzzing example
- [ ] Errors in the Fuzzer should exit the fuzz run
- [ ] More informative outpus, deeper introspection (stats, what mutation did x, etc.) - [ ] 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) - [ ] Timeout handling for llmp clients (no ping for n seconds -> treat as disconnected)
- [ ] LLMP Cross Machine Link (2 brokers connected via TCP) - [ ] LLMP Cross Machine Link (2 brokers connected via TCP)
- [ ] "Launcher" example that spawns broker + n clients - [ ] "Launcher" example that spawns broker + n clients
- [ ] Heap for signal handling (bumpallo or llmp directly?) - [ ] 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
View 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
View File

@ -0,0 +1 @@
book

13
docs/README.md Normal file
View 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
View 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
View 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
View File

@ -0,0 +1 @@
# Baby Fuzzer

View 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.

View 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.

View 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.

View 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]).

View 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.

View 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.

View 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, well show some commands used in the
> terminal. Lines that you should enter in a terminal all start with `$`. You
> dont need to type in the `$` character; it indicates the start of each
> command. Lines that dont 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
View 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
View 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.

View File

@ -0,0 +1,3 @@
# (De)Serialization
TODO describe the SerdeAny registry

View 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.

View File

@ -0,0 +1,3 @@
# Understanding Metadata
In this chapter, we discuss in depth the metadata system of LibAFL and its usage.

View File

@ -0,0 +1,3 @@
# Usage
TODO describe the HasMetadata interface

View 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/" }

View 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.

View 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
View File

@ -0,0 +1 @@
libpng-*

View 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"

View File

@ -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. 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. 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. 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 ## Build
To build this example, run `cargo build --example libfuzzer_libpng_cmpalloc --release`. 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. 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`. 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 ## Run

View File

@ -6,12 +6,28 @@ use std::{
process::{exit, Command}, process::{exit, Command},
}; };
use which::which;
const LIBPNG_URL: &str = const LIBPNG_URL: &str =
"https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz"; "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() { fn main() {
if cfg!(windows) { if cfg!(windows) {
println!("cargo:warning=Skipping libpng example on Windows"); println!("cargo:warning=Skipping libpng frida example on Windows");
exit(0); exit(0);
} }
@ -19,22 +35,32 @@ fn main() {
let cwd = env::current_dir().unwrap().to_string_lossy().to_string(); let cwd = env::current_dir().unwrap().to_string_lossy().to_string();
let out_dir = out_dir.to_string_lossy().to_string(); let out_dir = out_dir.to_string_lossy().to_string();
let out_dir_path = Path::new(&out_dir); 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=../libfuzzer_runtime/rt.c",);
println!("cargo:rerun-if-changed=harness.cc"); 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 = format!("{}/libpng-1.6.37", &out_dir);
let libpng_path = Path::new(&libpng); let libpng_path = Path::new(&libpng);
let libpng_tar = format!("{}/libpng-1.6.37.tar.xz", &cwd); let libpng_tar = format!("{}/libpng-1.6.37.tar.xz", &cwd);
// Enforce clang for its -fsanitize-coverage support. // Enforce clang for its -fsanitize-coverage support.
std::env::set_var("CC", "clang"); let clang = match env::var("CLANG_PATH") {
std::env::set_var("CXX", "clang++"); 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") { let ldflags = match env::var("LDFLAGS") {
Ok(val) => val, Ok(val) => val,
Err(_) => "".to_string(), Err(_) => "".to_string(),
}; };
// println!("cargo:warning=output path is {}", libpng);
if !libpng_path.is_dir() { if !libpng_path.is_dir() {
if !Path::new(&libpng_tar).is_file() { if !Path::new(&libpng_tar).is_file() {
println!("cargo:warning=Libpng not found, downloading..."); println!("cargo:warning=Libpng not found, downloading...");
@ -49,7 +75,7 @@ fn main() {
} }
Command::new("tar") Command::new("tar")
.current_dir(&out_dir_path) .current_dir(&out_dir_path)
.arg("-xvf") .arg("xvf")
.arg(&libpng_tar) .arg(&libpng_tar)
.status() .status()
.unwrap(); .unwrap();
@ -59,19 +85,20 @@ fn main() {
"--disable-shared", "--disable-shared",
&format!("--host={}", env::var("TARGET").unwrap())[..], &format!("--host={}", env::var("TARGET").unwrap())[..],
]) ])
.env("CC", "clang") .env("CC", &clang)
.env("CXX", "clang++") .env("CXX", &clangpp)
.env( .env(
"CFLAGS", "CFLAGS",
"-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard", "-O3 -g -D_DEFAULT_SOURCE -fPIC -fno-omit-frame-pointer",
) )
.env( .env(
"CXXFLAGS", "CXXFLAGS",
"-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard", "-O3 -g -D_DEFAULT_SOURCE -fPIC -fno-omit-frame-pointer",
) )
.env( .env(
"LDFLAGS", "LDFLAGS",
format!("-g -fPIE -fsanitize-coverage=trace-pc-guard {}", ldflags), //format!("-g -fPIE -fsanitize=address {}", ldflags),
format!("-g -fPIE {}", ldflags),
) )
.status() .status()
.unwrap(); .unwrap();
@ -81,29 +108,31 @@ fn main() {
.unwrap(); .unwrap();
} }
cc::Build::new() let status = cc::Build::new()
.file("../libfuzzer_runtime/rt.c")
.compile("libfuzzer-sys");
cc::Build::new()
.include(&libpng_path)
.cpp(true) .cpp(true)
.flag("-fsanitize-coverage=trace-pc-guard") .get_compiler()
// .define("HAS_DUMMY_CRASH", "1") .to_command()
.file("./harness.cc") .current_dir(&cwd)
.compile("libfuzzer-harness"); .arg("-I")
.arg(&libpng)
println!("cargo:rustc-link-search=native={}", &out_dir); //.arg("-D")
println!("cargo:rustc-link-search=native={}/.libs", &libpng); //.arg("HAS_DUMMY_CRASH=1")
println!("cargo:rustc-link-lib=static=png16"); .arg("-fPIC")
.arg("-shared")
//Deps for libpng: -pthread -lz -lm .arg("-O3")
println!("cargo:rustc-link-lib=dylib=m"); //.arg("-fomit-frame-pointer")
println!("cargo:rustc-link-lib=dylib=z"); .arg(if env::var("CARGO_CFG_TARGET_OS").unwrap() == "android" {
"-static-libstdc++"
//For the C++ harness } else {
//must by dylib for android ""
println!("cargo:rustc-link-lib=dylib=stdc++"); })
.arg("-o")
println!("cargo:rerun-if-changed=build.rs"); .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());
} }

View File

@ -17,6 +17,7 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
#include <stdlib.h>
#include <vector> #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; 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. // Entry point for LibFuzzer.
// Roughly follows the libpng book example: // Roughly follows the libpng book example:
// http://www.libpng.org/pub/png/book/chapter13.html // 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; return 0;
} }
func1();
std::vector<unsigned char> v(data, data + size); std::vector<unsigned char> v(data, data + size);
if (png_sig_cmp(v.data(), 0, kPngHeaderSize)) { if (png_sig_cmp(v.data(), 0, kPngHeaderSize)) {
// not a PNG. // not a PNG.

View 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(())
}

View 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.");
}

View File

@ -1 +1,2 @@
*.tar.gz *.tar.gz*
mozjpeg-4.0.3

View File

@ -1,31 +1,29 @@
[package] [package]
name = "libfuzzer_libmozjpeg" name = "libfuzzer_libmozjpeg"
version = "0.1.0" version = "0.1.0"
authors = ["Marcin Kozlowski <marcinguy@gmail.com>"] authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
edition = "2018" edition = "2018"
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]
default = ["std"] default = ["std"]
std = [] std = []
#[profile.release] [profile.release]
#lto = true lto = true
#codegen-units = 1 codegen-units = 1
#opt-level = 3 opt-level = 3
#debug = true 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] [build-dependencies]
cc = { version = "1.0", features = ["parallel"] } cc = { version = "1.0", features = ["parallel"] }
num_cpus = "1.0" num_cpus = "1.0"
[dependencies] [lib]
libafl = { path = "../../libafl/" }
[[example]]
name = "libfuzzer_libmozjpeg" name = "libfuzzer_libmozjpeg"
path = "./src/fuzzer.rs" crate-type = ["staticlib"]
test = false
bench = false

View File

@ -1,14 +1,39 @@
# Libfuzzer for libmozjpeg # Libfuzzer for libmozjpeg
This folder contains an example fuzzer for libmozjpeg, using LLMP for fast multi-process fuzzing and crash detection. 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. It has been tested on Linux.
## Build ## Build
To build this example, run `cargo build --example libfuzzer_libmozjpeg --release`. To build this example, run `cargo build --release`.
This will call (the build.rs)[./builld.rs], which in turn downloads a libmozjpeg archive from the web. 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.
Then, it will link (the fuzzer)[./src/fuzzer.rs] against (the c++ harness)[./harness.cc] and the instrumented `libmozjpeg`. 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.
Afterwards, the fuzzer will be ready to run, from `../../target/examples/libfuzzer_libmozjpeg`.
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 ## 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. 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. By restarting the actual fuzzer, it can recover from these exit conditions.
For convenience, you may just run `./test.sh` in this folder or: 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`.
broker.sh - starts the broker
start.sh - starts as many clients as there are cores
stop.sh - stop everything

View File

@ -1,3 +0,0 @@
#!/bin/bash
taskset -c 0 ./.libfuzzer_test.elf

View File

@ -1,119 +1,21 @@
// build.rs // build.rs
use std::{ use std::env;
env,
path::Path,
process::{exit, Command},
};
const LIBMOZJPEG_URL: &str = "https://github.com/mozilla/mozjpeg/archive/v4.0.3.tar.gz";
fn main() { 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 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 = 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.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();
}
cc::Build::new() cc::Build::new()
.file("../libfuzzer_runtime/rt.c") // Use sanitizer coverage to track the edges in the PUT
.compile("libfuzzer-sys"); // Take advantage of LTO (needs lld-link set in your cargo config)
//.flag("-flto=thin")
cc::Build::new() .file("./hook_allocs.c")
.include(&libmozjpeg_path) .compile("hook_allocs");
.flag("-fsanitize-coverage=trace-pc-guard")
.file("./harness.cc")
.compile("libfuzzer-harness");
println!("cargo:rustc-link-search=native={}", &out_dir); 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"); println!("cargo:rerun-if-changed=build.rs");
} }

View 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;
}

View 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();
}
}

View 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();
}
}

View File

@ -3,16 +3,14 @@
use std::{env, path::PathBuf}; use std::{env, path::PathBuf};
#[cfg(unix)]
use libafl::{ use libafl::{
bolts::{shmem::UnixShMem, tuples::tuple_list}, bolts::tuples::tuple_list,
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus, RandCorpusScheduler}, corpus::{Corpus, InMemoryCorpus, OnDiskCorpus, RandCorpusScheduler},
events::setup_restarting_mgr, events::setup_restarting_mgr_std,
executors::{inprocess::InProcessExecutor, Executor, ExitKind}, executors::{inprocess::InProcessExecutor, ExitKind},
feedbacks::{CrashFeedback, MaxMapFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback},
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer}, fuzzer::{Fuzzer, StdFuzzer},
inputs::Input, mutators::scheduled::{havoc_mutations, StdScheduledMutator},
mutators::scheduled::HavocBytesMutator,
mutators::token_mutations::Tokens, mutators::token_mutations::Tokens,
observers::StdMapObserver, observers::StdMapObserver,
stages::mutational::StdMutationalStage, stages::mutational::StdMutationalStage,
@ -22,35 +20,17 @@ use libafl::{
Error, Error,
}; };
/// We will interact with a C++ target, so use external c functionality use libafl_targets::{
#[cfg(unix)] 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" { extern "C" {
/// int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) static mut libafl_alloc_map: [usize; ALLOC_MAP_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
} }
/// The main fn, usually parsing parameters, and starting the fuzzer /// The main fn, usually parsing parameters, and starting the fuzzer
#[no_mangle]
pub fn main() { pub fn main() {
// Registry the metadata types used in this fuzzer // Registry the metadata types used in this fuzzer
// Needed only on no_std // Needed only on no_std
@ -68,27 +48,25 @@ pub fn main() {
.expect("An error occurred while fuzzing"); .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 /// The actual fuzzer
#[cfg(unix)]
fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> { 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 // 'While the stats are state, they are usually used in the broker - which is likely never restarted
let stats = SimpleStats::new(|s| println!("{}", s)); 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. // The restarting state will spawn the same process again as child, then restarted it each time it crashes.
let (state, mut restarting_mgr) = let (state, mut restarting_mgr) =
setup_restarting_mgr::<_, _, UnixShMem, _>(stats, broker_port) setup_restarting_mgr_std(stats, broker_port).expect("Failed to setup the restarter".into());
.expect("Failed to setup the restarter".into());
// Create an observation channel using the coverage map // Create an observation channel using the coverage map
let edges_observer = unsafe { let edges_observer =
StdMapObserver::new_from_ptr("edges", __lafl_edges_map, __lafl_max_edges_size as usize) 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 // If not restarting, create a State from scratch
let mut state = state.unwrap_or_else(|| { 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 // Corpus that will be evolved, we keep it in memory for performance
InMemoryCorpus::new(), InMemoryCorpus::new(),
// Feedbacks to rate the interestingness of an input // 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), // Corpus in which we store solutions (crashes in this example),
// on disk so the user can get them after stopping the fuzzer // on disk so the user can get them after stopping the fuzzer
OnDiskCorpus::new(objective_dir).unwrap(), 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 // Add the JPEG tokens if not existing
if state.metadata().get::<Tokens>().is_none() { 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 // Setup a basic mutator with a mutational stage
let mutator = HavocBytesMutator::default(); let mutator = StdScheduledMutator::new(havoc_mutations());
let stage = StdMutationalStage::new(mutator); let stage = StdMutationalStage::new(mutator);
// A fuzzer with just one stage and a random policy to get testcasess from the corpus // A random policy to get testcasess from the corpus
let fuzzer = StdFuzzer::new(RandCorpusScheduler::new(), tuple_list!(stage)); 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( let mut executor = InProcessExecutor::new(
"in-process(edges)", "in-process(edges,cmp,alloc)",
harness, &mut harness,
tuple_list!(edges_observer), tuple_list!(edges_observer, cmps_observer, allocs_observer),
&mut state, &mut state,
&mut restarting_mgr, &mut restarting_mgr,
)?; )?;
// The actual target run starts here. // The actual target run starts here.
// Call LLVMFUzzerInitialize() if present. // Call LLVMFUzzerInitialize() if present.
unsafe { let args: Vec<String> = env::args().collect();
if afl_libfuzzer_init() == -1 { if libfuzzer_initialize(&args) == -1 {
println!("Warning: LLVMFuzzerInitialize failed with -1") println!("Warning: LLVMFuzzerInitialize failed with -1")
} }
}
// In case the corpus is empty (on first run), reset // In case the corpus is empty (on first run), reset
if state.corpus().count() < 1 { if state.corpus().count() < 1 {
state state
.load_initial_inputs( .load_initial_inputs(&mut executor, &mut restarting_mgr, &scheduler, &corpus_dirs)
&mut executor,
&mut restarting_mgr,
fuzzer.scheduler(),
&corpus_dirs,
)
.expect(&format!( .expect(&format!(
"Failed to load initial corpus at {:?}", "Failed to load initial corpus at {:?}",
&corpus_dirs &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()); 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 // Never reached
Ok(()) Ok(())

View File

@ -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

View File

@ -1,2 +0,0 @@
#!/bin/bash
killall -9 .libfuzzer_test.elf

View File

@ -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

View File

@ -3,9 +3,6 @@ name = "libfuzzer_libpng"
version = "0.1.0" version = "0.1.0"
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"] authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
edition = "2018" edition = "2018"
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]
default = ["std"] default = ["std"]
@ -19,13 +16,15 @@ std = []
[build-dependencies] [build-dependencies]
cc = { version = "1.0", features = ["parallel"] } cc = { version = "1.0", features = ["parallel"] }
which = { version = "4.0.2" }
num_cpus = "1.0" num_cpus = "1.0"
[dependencies] [dependencies]
libafl = { path = "../../libafl/" } 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" name = "libfuzzer_libpng"
path = "./src/fuzzer.rs" crate-type = ["staticlib"]
test = false
bench = false

View File

@ -1,25 +1,79 @@
# Libfuzzer for libpng # Libfuzzer for libpng
This folder contains an example fuzzer for libpng, using LLMP for fast multi-process fuzzing and crash detection. 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. It has been tested on Linux.
## Build ## Build
To build this example, run `cargo build --example libfuzzer_libpng --release`. To build this example, run
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`. ```bash
Afterwards, the fuzzer will be ready to run, from `../../target/examples/libfuzzer_libpng`. 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 ## 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`). 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. 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. 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). 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.

View 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");
}
}

View 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");
}
}

View File

@ -4,19 +4,18 @@
use core::time::Duration; use core::time::Duration;
use std::{env, path::PathBuf}; use std::{env, path::PathBuf};
#[cfg(unix)]
use libafl::{ use libafl::{
bolts::{shmem::UnixShMem, tuples::tuple_list}, bolts::tuples::tuple_list,
corpus::{ corpus::{
Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus,
QueueCorpusScheduler, QueueCorpusScheduler,
}, },
events::setup_restarting_mgr, events::{setup_restarting_mgr_std, EventManager},
executors::{inprocess::InProcessExecutor, Executor, ExitKind, TimeoutExecutor}, executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor},
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer}, fuzzer::{Fuzzer, StdFuzzer},
inputs::Input, mutators::scheduled::{havoc_mutations, StdScheduledMutator},
mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens}, mutators::token_mutations::Tokens,
observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, observers::{HitcountsMapObserver, StdMapObserver, TimeObserver},
stages::mutational::StdMutationalStage, stages::mutational::StdMutationalStage,
state::{HasCorpus, HasMetadata, State}, state::{HasCorpus, HasMetadata, State},
@ -25,35 +24,10 @@ use libafl::{
Error, Error,
}; };
/// We will interact with a C++ target, so use external c functionality use libafl_targets::{libfuzzer_initialize, libfuzzer_test_one_input, EDGES_MAP, MAX_EDGES_NUM};
#[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() /// The main fn, no_mangle as it is a C main
fn afl_libfuzzer_init() -> i32; #[no_mangle]
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
pub fn main() { pub fn main() {
// Registry the metadata types used in this fuzzer // Registry the metadata types used in this fuzzer
// Needed only on no_std // Needed only on no_std
@ -71,21 +45,13 @@ pub fn main() {
.expect("An error occurred while fuzzing"); .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 /// The actual fuzzer
#[cfg(unix)]
fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> { 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 // 'While the stats are state, they are usually used in the broker - which is likely never restarted
let stats = SimpleStats::new(|s| println!("{}", s)); 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. // The restarting state will spawn the same process again as child, then restarted it each time it crashes.
let (state, mut restarting_mgr) = let (state, mut restarting_mgr) = match setup_restarting_mgr_std(stats, broker_port) {
match setup_restarting_mgr::<_, _, UnixShMem, _>(stats, broker_port) {
Ok(res) => res, Ok(res) => res,
Err(err) => match err { Err(err) => match err {
Error::ShuttingDown => { Error::ShuttingDown => {
@ -99,7 +65,7 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
// Create an observation channel using the coverage map // Create an observation channel using the coverage map
let edges_observer = HitcountsMapObserver::new(unsafe { 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 // 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 // Setup a basic mutator with a mutational stage
let mutator = HavocBytesMutator::default(); let mutator = StdScheduledMutator::new(havoc_mutations());
let stage = StdMutationalStage::new(mutator); let stage = StdMutationalStage::new(mutator);
// A fuzzer with just one stage and a minimization+queue policy to get testcasess from the corpus // A fuzzer with just one stage
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new()); let mut fuzzer = StdFuzzer::new(tuple_list!(stage));
let fuzzer = StdFuzzer::new(scheduler, 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( let mut executor = TimeoutExecutor::new(
InProcessExecutor::new( InProcessExecutor::new(
"in-process(edges)", "in-process(edges,time)",
harness, &mut harness,
tuple_list!(edges_observer, TimeObserver::new("time")), tuple_list!(edges_observer, TimeObserver::new("time")),
&mut state, &mut state,
&mut restarting_mgr, &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. // The actual target run starts here.
// Call LLVMFUzzerInitialize() if present. // Call LLVMFUzzerInitialize() if present.
unsafe { let args: Vec<String> = env::args().collect();
if afl_libfuzzer_init() == -1 { if libfuzzer_initialize(&args) == -1 {
println!("Warning: LLVMFuzzerInitialize failed with -1") println!("Warning: LLVMFuzzerInitialize failed with -1")
} }
}
// In case the corpus is empty (on first run), reset // In case the corpus is empty (on first run), reset
if state.corpus().count() < 1 { if state.corpus().count() < 1 {
state state
.load_initial_inputs( .load_initial_inputs(&mut executor, &mut restarting_mgr, &scheduler, &corpus_dirs)
&mut executor,
&mut restarting_mgr,
fuzzer.scheduler(),
&corpus_dirs,
)
.expect(&format!( .expect(&format!(
"Failed to load initial corpus at {:?}", "Failed to load initial corpus at {:?}",
&corpus_dirs &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()); 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(()) Ok(())
} }

View File

@ -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

View File

@ -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");
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -0,0 +1 @@
libpng-*

View File

@ -1,31 +1,24 @@
[package] [package]
name = "libfuzzer_libpng_cmpalloc" name = "libfuzzer_stb_image"
version = "0.1.0" version = "0.1.0"
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"] authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
edition = "2018" edition = "2018"
build = "build.rs" build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]
default = ["std"] default = ["std"]
std = [] std = []
#[profile.release] [profile.release]
#lto = true lto = true
#codegen-units = 1 codegen-units = 1
#opt-level = 3 opt-level = 3
#debug = true debug = true
[dependencies]
libafl = { path = "../../libafl/" }
libafl_targets = { path = "../../libafl_targets/", features = ["pcguard_edges", "libfuzzer"] }
[build-dependencies] [build-dependencies]
cc = { version = "1.0", features = ["parallel"] } cc = { version = "1.0", features = ["parallel"] }
num_cpus = "1.0" num_cpus = "1.0"
[dependencies]
libafl = { path = "../../libafl/" }
[[example]]
name = "libfuzzer_libpng_cmpalloc"
path = "./src/fuzzer.rs"
test = false
bench = false

View 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).

View 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");
}

View File

Before

Width:  |  Height:  |  Size: 218 B

After

Width:  |  Height:  |  Size: 218 B

View File

Before

Width:  |  Height:  |  Size: 376 B

After

Width:  |  Height:  |  Size: 376 B

View File

Before

Width:  |  Height:  |  Size: 228 B

After

Width:  |  Height:  |  Size: 228 B

View File

Before

Width:  |  Height:  |  Size: 427 B

After

Width:  |  Height:  |  Size: 427 B

View 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;
}

View File

@ -1,22 +1,21 @@
//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts //! 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}; use std::{env, path::PathBuf};
#[cfg(unix)]
use libafl::{ use libafl::{
bolts::{shmem::UnixShMem, tuples::tuple_list}, bolts::tuples::tuple_list,
corpus::{ corpus::{
Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus,
QueueCorpusScheduler, QueueCorpusScheduler,
}, },
events::setup_restarting_mgr, events::setup_restarting_mgr_std,
executors::{inprocess::InProcessExecutor, Executor, ExitKind}, executors::{inprocess::InProcessExecutor, ExitKind},
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer}, fuzzer::{Fuzzer, StdFuzzer},
inputs::Input, mutators::scheduled::{havoc_mutations, StdScheduledMutator},
mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens}, mutators::token_mutations::Tokens,
observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, observers::{StdMapObserver, TimeObserver},
stages::mutational::StdMutationalStage, stages::mutational::StdMutationalStage,
state::{HasCorpus, HasMetadata, State}, state::{HasCorpus, HasMetadata, State},
stats::SimpleStats, stats::SimpleStats,
@ -24,38 +23,8 @@ use libafl::{
Error, 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() { pub fn main() {
// Registry the metadata types used in this fuzzer // Registry the metadata types used in this fuzzer
// Needed only on no_std // Needed only on no_std
@ -73,21 +42,13 @@ pub fn main() {
.expect("An error occurred while fuzzing"); .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 /// The actual fuzzer
#[cfg(unix)]
fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> { 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 // 'While the stats are state, they are usually used in the broker - which is likely never restarted
let stats = SimpleStats::new(|s| println!("{}", s)); 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. // The restarting state will spawn the same process again as child, then restarted it each time it crashes.
let (state, mut restarting_mgr) = let (state, mut restarting_mgr) = match setup_restarting_mgr_std(stats, broker_port) {
match setup_restarting_mgr::<_, _, UnixShMem, _>(stats, broker_port) {
Ok(res) => res, Ok(res) => res,
Err(err) => match err { Err(err) => match err {
Error::ShuttingDown => { Error::ShuttingDown => {
@ -100,16 +61,9 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
}; };
// Create an observation channel using the coverage map // Create an observation channel using the coverage map
let edges_observer = HitcountsMapObserver::new(unsafe { // We don't use the hitcounts (see the Cargo.toml, we use pcguard_edges)
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 = 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) };
// If not restarting, create a State from scratch // If not restarting, create a State from scratch
let mut state = state.unwrap_or_else(|| { 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 // Feedbacks to rate the interestingness of an input
tuple_list!( tuple_list!(
MaxMapFeedback::new_with_observer_track(&edges_observer, true, false), MaxMapFeedback::new_with_observer_track(&edges_observer, true, false),
MaxMapFeedback::new_with_observer(&cmps_observer),
MaxMapFeedback::new_with_observer(&allocs_observer),
TimeFeedback::new() TimeFeedback::new()
), ),
// Corpus in which we store solutions (crashes in this example), // 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 // Setup a basic mutator with a mutational stage
let mutator = HavocBytesMutator::default(); let mutator = StdScheduledMutator::new(havoc_mutations());
let stage = StdMutationalStage::new(mutator); let stage = StdMutationalStage::new(mutator);
// A fuzzer with just one stage and a minimization+queue policy to get testcasess from the corpus // 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 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 // Create the executor for an in-process function with just one observer for edge coverage
let mut executor = InProcessExecutor::new( let mut executor = InProcessExecutor::new(
"in-process(edges,cmps,allocs)", "in-process(edges,time)",
harness, &mut harness,
tuple_list!( tuple_list!(edges_observer, TimeObserver::new("time")),
edges_observer,
cmps_observer,
allocs_observer,
TimeObserver::new("time")
),
&mut state, &mut state,
&mut restarting_mgr, &mut restarting_mgr,
)?; )?;
// The actual target run starts here. // The actual target run starts here.
// Call LLVMFUzzerInitialize() if present. // Call LLVMFUzzerInitialize() if present.
unsafe { let args: Vec<String> = env::args().collect();
if afl_libfuzzer_init() == -1 { if libfuzzer_initialize(&args) == -1 {
println!("Warning: LLVMFuzzerInitialize failed with -1") println!("Warning: LLVMFuzzerInitialize failed with -1")
} }
}
// In case the corpus is empty (on first run), reset // In case the corpus is empty (on first run), reset
if state.corpus().count() < 1 { if state.corpus().count() < 1 {
state state
.load_initial_inputs( .load_initial_inputs(&mut executor, &mut restarting_mgr, &scheduler, &corpus_dirs)
&mut executor,
&mut restarting_mgr,
fuzzer.scheduler(),
&corpus_dirs,
)
.expect(&format!( .expect(&format!(
"Failed to load initial corpus at {:?}", "Failed to load initial corpus at {:?}",
&corpus_dirs &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()); 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 // Never reached
Ok(()) Ok(())

File diff suppressed because it is too large Load Diff

View 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"]

View File

@ -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

Binary file not shown.

View File

@ -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(&regs);
}
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");
}

View File

@ -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],
}

View File

@ -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);
}

View File

@ -2,6 +2,11 @@
name = "libafl" name = "libafl"
version = "0.1.0" version = "0.1.0"
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"] 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" edition = "2018"
build = "build.rs" build = "build.rs"
@ -13,7 +18,6 @@ ahash = "0.6.1" # another hash
fxhash = "0.2.1" # yet another hash fxhash = "0.2.1" # yet another hash
xxhash-rust = { version = "0.8.0", features = ["const_xxh3", "xxh3"] } # xxh3 hashing for rust xxhash-rust = { version = "0.8.0", features = ["const_xxh3", "xxh3"] } # xxh3 hashing for rust
serde_json = "1.0.60" serde_json = "1.0.60"
num_cpus = "1.0" # cpu count, for llmp example num_cpus = "1.0" # cpu count, for llmp example
[[bench]] [[bench]]
@ -30,12 +34,13 @@ harness = false
#debug = true #debug = true
[features] [features]
default = ["std", "anymapdbg", "derive"] default = ["std", "anymap_debug", "derive", "llmp_compression"]
std = [] # print, sharedmap, ... support std = [] # print, sharedmap, ... support
runtime = [] # a runtime for clang inmem-executor anymap_debug = ["serde_json"] # uses serde_json to Debug the anymap trait. Disable for smaller footprint.
anymapdbg = ["serde_json"] # uses serde_json to Debug the anymap trait. Disable for smaller footprint.
derive = ["libafl_derive"] # provide derive(SerdeAny) macro. derive = ["libafl_derive"] # provide derive(SerdeAny) macro.
llmp_small_maps = [] # reduces initial map size for llmp llmp_small_maps = [] # reduces initial map size for llmp
llmp_debug = ["backtrace"] # Enables debug output for LLMP
llmp_compression = [] #llmp compression using GZip
[[example]] [[example]]
name = "llmp_test" name = "llmp_test"
@ -46,7 +51,7 @@ required-features = ["std"]
tuple_list = "0.1.2" tuple_list = "0.1.2"
hashbrown = { version = "0.9", features = ["serde", "ahash-compile-time-rng"] } # A faster hashmap, nostd compatible hashbrown = { version = "0.9", features = ["serde", "ahash-compile-time-rng"] } # A faster hashmap, nostd compatible
num = "*" 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 serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib
erased-serde = "0.3.12" erased-serde = "0.3.12"
postcard = { version = "0.5.1", features = ["alloc"] } # no_std compatible serde serialization fromat postcard = { version = "0.5.1", features = ["alloc"] } # no_std compatible serde serialization fromat
@ -54,13 +59,22 @@ static_assertions = "1.1.0"
ctor = "*" ctor = "*"
libafl_derive = { version = "*", optional = true, path = "../libafl_derive" } 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 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" 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] [target.'cfg(unix)'.dependencies]
libc = "0.2" # For (*nix) libc libc = "0.2" # For (*nix) libc
nix = "0.20.0" nix = "0.20.0"
uds = "0.2.3" uds = "0.2.3"
lock_api = "0.4.3"
regex = "1.4.5"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
windows = "0.4.0" windows = "0.4.0"

View File

@ -1,3 +1,5 @@
//! special handling to build and link libafl
fn main() { fn main() {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
windows::build!( windows::build!(

View File

@ -11,7 +11,10 @@ use std::{thread, time};
use libafl::bolts::llmp::Tag; use libafl::bolts::llmp::Tag;
#[cfg(all(unix, feature = "std"))] #[cfg(all(unix, feature = "std"))]
use libafl::{ use libafl::{
bolts::{llmp, shmem::UnixShMem}, bolts::{
llmp,
shmem::{ShMemProvider, StdShMemProvider},
},
Error, Error,
}; };
@ -21,7 +24,8 @@ const _TAG_1MEG_V1: Tag = 0xB1111161;
#[cfg(all(unix, feature = "std"))] #[cfg(all(unix, feature = "std"))]
fn adder_loop(port: u16) -> ! { 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 last_result: u32 = 0;
let mut current_result: u32 = 0; let mut current_result: u32 = 0;
loop { loop {
@ -63,7 +67,8 @@ fn adder_loop(port: u16) -> ! {
#[cfg(all(unix, feature = "std"))] #[cfg(all(unix, feature = "std"))]
fn large_msg_loop(port: u16) -> ! { 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]; let meg_buf = [1u8; 1 << 20];
@ -78,6 +83,7 @@ fn large_msg_loop(port: u16) -> ! {
fn broker_message_hook( fn broker_message_hook(
client_id: u32, client_id: u32,
tag: llmp::Tag, tag: llmp::Tag,
_flags: llmp::Flag,
message: &[u8], message: &[u8],
) -> Result<llmp::LlmpMsgHookResult, Error> { ) -> Result<llmp::LlmpMsgHookResult, Error> {
match tag { match tag {
@ -124,7 +130,7 @@ fn main() {
match mode.as_str() { match mode.as_str() {
"broker" => { "broker" => {
let mut broker = llmp::LlmpBroker::<UnixShMem>::new().unwrap(); let mut broker = llmp::LlmpBroker::new(StdShMemProvider::new().unwrap()).unwrap();
broker broker
.launch_listener(llmp::Listener::Tcp( .launch_listener(llmp::Listener::Tcp(
std::net::TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap(), 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))) broker.loop_forever(&mut broker_message_hook, Some(Duration::from_millis(5)))
} }
"ctr" => { "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; let mut counter: u32 = 0;
loop { loop {
counter = counter.wrapping_add(1); counter = counter.wrapping_add(1);

View File

@ -1,2 +1,4 @@
//! Generated bindings
#[cfg(all(windows, feature = "std"))] #[cfg(all(windows, feature = "std"))]
::windows::include_bindings!(); ::windows::include_bindings!();

View 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());
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,10 @@
//! Bolts are no conceptual fuzzing elements, but they keep libafl-based fuzzers together. //! Bolts are no conceptual fuzzing elements, but they keep libafl-based fuzzers together.
pub mod bindings; pub mod bindings;
#[cfg(feature = "llmp_compression")]
pub mod compress;
pub mod llmp; pub mod llmp;
pub mod os; pub mod os;
pub mod ownedref; pub mod ownedref;

View 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);
}
}
}
}
}

View File

@ -1,3 +1,8 @@
//! Operating System specific abstractions
#[cfg(all(unix, feature = "std"))]
pub mod ashmem_server;
#[cfg(unix)] #[cfg(unix)]
pub mod unix_signals; pub mod unix_signals;
#[cfg(windows)] #[cfg(windows)]

View File

@ -1,3 +1,4 @@
//! Signal handling for unix
use alloc::vec::Vec; use alloc::vec::Vec;
use core::{ use core::{
cell::UnsafeCell, cell::UnsafeCell,
@ -12,9 +13,9 @@ use core::{
use std::ffi::CString; use std::ffi::CString;
use libc::{ use libc::{
c_int, malloc, sigaction, sigaltstack, sigemptyset, stack_t, SA_NODEFER, SA_ONSTACK, c_int, malloc, sigaction, sigaltstack, sigemptyset, stack_t, ucontext_t, SA_NODEFER,
SA_SIGINFO, SIGABRT, SIGALRM, SIGBUS, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGKILL, SIGPIPE, SA_ONSTACK, SA_SIGINFO, SIGABRT, SIGALRM, SIGBUS, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGKILL,
SIGQUIT, SIGSEGV, SIGTERM, SIGUSR2, SIGPIPE, SIGQUIT, SIGSEGV, SIGTERM, SIGTRAP, SIGUSR2,
}; };
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
@ -24,6 +25,7 @@ pub use libc::{c_void, siginfo_t};
#[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy)] #[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy)]
#[repr(i32)] #[repr(i32)]
#[allow(clippy::clippy::pub_enum_variant_names)]
pub enum Signal { pub enum Signal {
SigAbort = SIGABRT, SigAbort = SIGABRT,
SigBus = SIGBUS, SigBus = SIGBUS,
@ -38,6 +40,7 @@ pub enum Signal {
SigQuit = SIGQUIT, SigQuit = SIGQUIT,
SigTerm = SIGTERM, SigTerm = SIGTERM,
SigInterrupt = SIGINT, SigInterrupt = SIGINT,
SigTrap = SIGTRAP,
} }
pub static CRASH_SIGNALS: &[Signal] = &[ pub static CRASH_SIGNALS: &[Signal] = &[
@ -75,6 +78,7 @@ impl Display for Signal {
Signal::SigQuit => write!(f, "SIGQUIT")?, Signal::SigQuit => write!(f, "SIGQUIT")?,
Signal::SigTerm => write!(f, "SIGTERM")?, Signal::SigTerm => write!(f, "SIGTERM")?,
Signal::SigInterrupt => write!(f, "SIGINT")?, Signal::SigInterrupt => write!(f, "SIGINT")?,
Signal::SigTrap => write!(f, "SIGTRAP")?,
}; };
Ok(()) Ok(())
@ -83,7 +87,7 @@ impl Display for Signal {
pub trait Handler { pub trait Handler {
/// Handle a signal /// 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 /// Return a list of signals to handle
fn signals(&self) -> Vec<Signal>; fn signals(&self) -> Vec<Signal>;
} }
@ -111,7 +115,7 @@ static mut SIGNAL_HANDLERS: [Option<HandlerHolder>; 32] = [
/// # Safety /// # Safety
/// This should be somewhat safe to call for signals previously registered, /// This should be somewhat safe to call for signals previously registered,
/// unless the signal handlers registered using [setup_signal_handler] are broken. /// 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 signal = &Signal::try_from(sig).unwrap();
let handler = { let handler = {
match &SIGNAL_HANDLERS[*signal as usize] { 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, 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. /// Setup signal handlers in a somewhat rusty way.

View File

@ -1,3 +1,5 @@
//! Exception handling for Windows
pub use crate::bolts::bindings::windows::win32::debug::EXCEPTION_POINTERS; pub use crate::bolts::bindings::windows::win32::debug::EXCEPTION_POINTERS;
use crate::{bolts::bindings::windows::win32::debug::SetUnhandledExceptionFilter, Error}; use crate::{bolts::bindings::windows::win32::debug::SetUnhandledExceptionFilter, Error};
@ -7,6 +9,7 @@ use core::{
cell::UnsafeCell, cell::UnsafeCell,
convert::TryFrom, convert::TryFrom,
fmt::{self, Display, Formatter}, fmt::{self, Display, Formatter},
ptr,
ptr::write_volatile, ptr::write_volatile,
sync::atomic::{compiler_fence, Ordering}, sync::atomic::{compiler_fence, Ordering},
}; };
@ -14,10 +17,21 @@ use std::os::raw::{c_long, c_void};
use num_enum::{IntoPrimitive, TryFromPrimitive}; 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_CONTINUE_SEARCH: c_long = 0;
const EXCEPTION_EXECUTE_HANDLER: c_long = 1; const EXCEPTION_EXECUTE_HANDLER: c_long = 1;
// From https://github.com/Alexpux/mingw-w64/blob/master/mingw-w64-headers/crt/signal.h
pub const SIGINT: i32 = 2;
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 // From https://github.com/wine-mirror/wine/blob/master/include/winnt.h#L611
pub const STATUS_WAIT_0: u32 = 0x00000000; pub const STATUS_WAIT_0: u32 = 0x00000000;
pub const STATUS_ABANDONED_WAIT_0: u32 = 0x00000080; 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, 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; type NativeHandlerType = extern "system" fn(*mut EXCEPTION_POINTERS) -> c_long;
static mut PREVIOUS_HANDLER: Option<NativeHandlerType> = None; static mut PREVIOUS_HANDLER: Option<NativeHandlerType> = None;
@ -287,18 +319,8 @@ unsafe extern "system" fn handle_exception(exception_pointers: *mut EXCEPTION_PO
.unwrap() .unwrap()
.exception_code; .exception_code;
let exception_code = ExceptionCode::try_from(code).unwrap(); let exception_code = ExceptionCode::try_from(code).unwrap();
let index = EXCEPTION_CODES_MAPPING // println!("Received {}", exception_code);
.iter() let ret = internal_handle_exception(exception_code, exception_pointers);
.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,
};
if let Some(prev_handler) = PREVIOUS_HANDLER { if let Some(prev_handler) = PREVIOUS_HANDLER {
prev_handler(exception_pointers) prev_handler(exception_pointers)
} else { } 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. /// 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> { pub unsafe fn setup_exception_handler<T: 'static + Handler>(handler: &mut T) -> Result<(), Error> {
let exceptions = handler.exceptions(); let exceptions = handler.exceptions();
let mut catch_assertions = false;
for exception_code in exceptions { for exception_code in exceptions {
if exception_code == ExceptionCode::AssertionFailure {
catch_assertions = true;
}
let index = EXCEPTION_CODES_MAPPING let index = EXCEPTION_CODES_MAPPING
.iter() .iter()
.position(|x| *x == exception_code) .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); compiler_fence(Ordering::SeqCst);
if catch_assertions {
signal(SIGABRT, handle_signal);
}
if let Some(prev) = SetUnhandledExceptionFilter(Some(core::mem::transmute( if let Some(prev) = SetUnhandledExceptionFilter(Some(core::mem::transmute(
handle_exception as *const c_void, handle_exception as *const c_void,
))) { ))) {

View File

@ -5,66 +5,102 @@ use alloc::{boxed::Box, vec::Vec};
use core::{clone::Clone, fmt::Debug}; use core::{clone::Clone, fmt::Debug};
use serde::{Deserialize, Deserializer, Serialize, Serializer}; 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 /// Wrap a reference and convert to a Box on serialize
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Ptr<'a, T: 'a + ?Sized> { pub enum OwnedRef<'a, T>
where
T: 'a + ?Sized,
{
Ref(&'a T), Ref(&'a T),
Owned(Box<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> fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
{ {
match self { match self {
Ptr::Ref(r) => r.serialize(se), OwnedRef::Ref(r) => r.serialize(se),
Ptr::Owned(b) => b.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 where
T: 'a + ?Sized,
Box<T>: Deserialize<'de>, Box<T>: Deserialize<'de>,
{ {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
D: Deserializer<'de>, 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 { fn as_ref(&self) -> &T {
match self { match self {
Ptr::Ref(r) => r, OwnedRef::Ref(r) => r,
Ptr::Owned(v) => v.as_ref(), 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 /// Wrap a mutable reference and convert to a Box on serialize
#[derive(Debug)] #[derive(Debug)]
pub enum PtrMut<'a, T: 'a + ?Sized> { pub enum OwnedRefMut<'a, T: 'a + ?Sized> {
Ref(&'a mut T), Ref(&'a mut T),
Owned(Box<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> fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
{ {
match self { match self {
PtrMut::Ref(r) => r.serialize(se), OwnedRefMut::Ref(r) => r.serialize(se),
PtrMut::Owned(b) => b.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 where
Box<T>: Deserialize<'de>, Box<T>: Deserialize<'de>,
{ {
@ -72,48 +108,67 @@ where
where where
D: Deserializer<'de>, 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 { fn as_ref(&self) -> &T {
match self { match self {
PtrMut::Ref(r) => r, OwnedRefMut::Ref(r) => r,
PtrMut::Owned(v) => v.as_ref(), 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 { fn as_mut(&mut self) -> &mut T {
match self { match self {
PtrMut::Ref(r) => r, OwnedRefMut::Ref(r) => r,
PtrMut::Owned(v) => v.as_mut(), 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 /// Wrap a slice and convert to a Vec on serialize
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Slice<'a, T: 'a + Sized> { pub enum OwnedSlice<'a, T: 'a + Sized> {
Ref(&'a [T]), Ref(&'a [T]),
Owned(Vec<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> fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
{ {
match self { match self {
Slice::Ref(r) => r.serialize(se), OwnedSlice::Ref(r) => r.serialize(se),
Slice::Owned(b) => b.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 where
Vec<T>: Deserialize<'de>, Vec<T>: Deserialize<'de>,
{ {
@ -121,39 +176,58 @@ where
where where
D: Deserializer<'de>, 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] { pub fn as_slice(&self) -> &[T] {
match self { match self {
Slice::Ref(r) => r, OwnedSlice::Ref(r) => r,
Slice::Owned(v) => v.as_slice(), 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 /// Wrap a mutable slice and convert to a Vec on serialize
#[derive(Debug)] #[derive(Debug)]
pub enum SliceMut<'a, T: 'a + Sized> { pub enum OwnedSliceMut<'a, T: 'a + Sized> {
Ref(&'a mut [T]), Ref(&'a mut [T]),
Owned(Vec<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> fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
{ {
match self { match self {
SliceMut::Ref(r) => r.serialize(se), OwnedSliceMut::Ref(r) => r.serialize(se),
SliceMut::Owned(b) => b.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 where
Vec<T>: Deserialize<'de>, Vec<T>: Deserialize<'de>,
{ {
@ -161,34 +235,53 @@ where
where where
D: Deserializer<'de>, 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] { pub fn as_slice(&self) -> &[T] {
match self { match self {
SliceMut::Ref(r) => r, OwnedSliceMut::Ref(r) => r,
SliceMut::Owned(v) => v.as_slice(), 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 { match self {
SliceMut::Ref(r) => r, OwnedSliceMut::Ref(r) => r,
SliceMut::Owned(v) => v.as_mut_slice(), 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 /// Wrap a C-style pointer and convert to a Box on serialize
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Cptr<T: Sized> { pub enum OwnedPtr<T: Sized> {
Cptr(*const T), Ptr(*const T),
Owned(Box<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> fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, 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 where
Vec<T>: Deserialize<'de>, Vec<T>: Deserialize<'de>,
{ {
@ -205,27 +298,46 @@ where
where where
D: Deserializer<'de>, 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 { fn as_ref(&self) -> &T {
match self { match self {
Cptr::Cptr(p) => unsafe { p.as_ref().unwrap() }, OwnedPtr::Ptr(p) => unsafe { p.as_ref().unwrap() },
Cptr::Owned(v) => v.as_ref(), 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 /// Wrap a C-style mutable pointer and convert to a Box on serialize
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum CptrMut<T: Sized> { pub enum OwnedPtrMut<T: Sized> {
Cptr(*mut T), Ptr(*mut T),
Owned(Box<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> fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, 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 where
Vec<T>: Deserialize<'de>, Vec<T>: Deserialize<'de>,
{ {
@ -242,36 +354,57 @@ where
where where
D: Deserializer<'de>, 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 { fn as_ref(&self) -> &T {
match self { match self {
CptrMut::Cptr(p) => unsafe { p.as_ref().unwrap() }, OwnedPtrMut::Ptr(p) => unsafe { p.as_ref().unwrap() },
CptrMut::Owned(b) => b.as_ref(), 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 { fn as_mut(&mut self) -> &mut T {
match self { match self {
CptrMut::Cptr(p) => unsafe { p.as_mut().unwrap() }, OwnedPtrMut::Ptr(p) => unsafe { p.as_mut().unwrap() },
CptrMut::Owned(b) => b.as_mut(), 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 /// Wrap a C-style pointer to an array (with size= and convert to a Vec on serialize
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Array<T: Sized> { pub enum OwnedArrayPtr<T: Sized> {
Cptr((*const T, usize)), ArrayPtr((*const T, usize)),
Owned(Vec<T>), 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> fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, 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 where
Vec<T>: Deserialize<'de>, Vec<T>: Deserialize<'de>,
{ {
@ -288,27 +421,48 @@ where
where where
D: Deserializer<'de>, 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] { pub fn as_slice(&self) -> &[T] {
match self { match self {
Array::Cptr(p) => unsafe { core::slice::from_raw_parts(p.0, p.1) }, OwnedArrayPtr::ArrayPtr(p) => unsafe { core::slice::from_raw_parts(p.0, p.1) },
Array::Owned(v) => v.as_slice(), 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 /// Wrap a C-style mutable pointer to an array (with size= and convert to a Vec on serialize
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum ArrayMut<T: Sized> { pub enum OwnedArrayPtrMut<T: Sized> {
Cptr((*mut T, usize)), ArrayPtr((*mut T, usize)),
Owned(Vec<T>), 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> fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, 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 where
Vec<T>: Deserialize<'de>, Vec<T>: Deserialize<'de>,
{ {
@ -325,22 +479,43 @@ where
where where
D: Deserializer<'de>, 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] { pub fn as_slice(&self) -> &[T] {
match self { match self {
ArrayMut::Cptr(p) => unsafe { core::slice::from_raw_parts(p.0, p.1) }, OwnedArrayPtrMut::ArrayPtr(p) => unsafe { core::slice::from_raw_parts(p.0, p.1) },
ArrayMut::Owned(v) => v.as_slice(), OwnedArrayPtrMut::Owned(v) => v.as_slice(),
} }
} }
pub fn as_mut_slice(&mut self) -> &mut [T] { pub fn as_mut_slice(&mut self) -> &mut [T] {
match self { match self {
ArrayMut::Cptr(p) => unsafe { core::slice::from_raw_parts_mut(p.0, p.1) }, OwnedArrayPtrMut::ArrayPtr(p) => unsafe { core::slice::from_raw_parts_mut(p.0, p.1) },
ArrayMut::Owned(v) => v.as_mut_slice(), 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),
} }
} }
} }

View File

@ -5,9 +5,6 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
use alloc::boxed::Box; use alloc::boxed::Box;
use core::any::{Any, TypeId}; use core::any::{Any, TypeId};
#[cfg(feature = "anymap_debug")]
use serde_json;
// yolo // yolo
pub fn pack_type_id(id: u64) -> TypeId { 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 { impl fmt::Debug for SerdeAnyMap {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let json = serde_json::to_string(&self); 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 { impl fmt::Debug for SerdeAnyMap {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "SerdeAnymap with {} elements", self.len()) write!(f, "SerdeAnymap with {} elements", self.len())
@ -277,7 +274,7 @@ macro_rules! create_serde_registry_for_trait {
None => None, None => None,
Some(h) => h Some(h) => h
.get(&xxhash_rust::xxh3::xxh3_64(name.as_bytes())) .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, None => None,
Some(h) => h Some(h) => h
.get_mut(&xxhash_rust::xxh3::xxh3_64(name.as_bytes())) .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); create_serde_registry_for_trait!(serdeany_registry, crate::bolts::serdeany::SerdeAny);
pub use serdeany_registry::*; pub use serdeany_registry::*;
#[cfg(feature = "std")]
#[macro_export] #[macro_export]
macro_rules! impl_serdeany { macro_rules! impl_serdeany {
($struct_name:ident) => { ($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 { fn as_any(&self) -> &dyn core::any::Any {
self self
} }
@ -510,10 +508,25 @@ macro_rules! impl_serdeany {
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[cfg(feature = "std")] #[$crate::ctor]
#[ctor]
fn $struct_name() { 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
}
} }
}; };
} }

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,11 @@ pub use tuple_list::{tuple_list, tuple_list_type, TupleList};
use core::any::TypeId; use core::any::TypeId;
use xxhash_rust::const_xxh3::xxh3_64;
pub trait HasLen { pub trait HasLen {
const LEN: usize;
fn len(&self) -> usize; fn len(&self) -> usize;
fn is_empty(&self) -> bool { fn is_empty(&self) -> bool {
self.len() == 0 self.len() == 0
@ -12,6 +16,8 @@ pub trait HasLen {
} }
impl HasLen for () { impl HasLen for () {
const LEN: usize = 0;
fn len(&self) -> usize { fn len(&self) -> usize {
0 0
} }
@ -19,13 +25,61 @@ impl HasLen for () {
impl<Head, Tail> HasLen for (Head, Tail) impl<Head, Tail> HasLen for (Head, Tail)
where where
Tail: TupleList + HasLen, Tail: HasLen,
{ {
const LEN: usize = 1 + Tail::LEN;
fn len(&self) -> usize { fn len(&self) -> usize {
1 + self.1.len() 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 { pub trait MatchFirstType {
fn match_first_type<T: 'static>(&self) -> Option<&T>; fn match_first_type<T: 'static>(&self) -> Option<&T>;
fn match_first_type_mut<T: 'static>(&mut self) -> Option<&mut 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) impl<Head, Tail> MatchFirstType for (Head, Tail)
where where
Head: 'static, Head: 'static,
Tail: TupleList + MatchFirstType, Tail: MatchFirstType,
{ {
fn match_first_type<T: 'static>(&self) -> Option<&T> { fn match_first_type<T: 'static>(&self) -> Option<&T> {
if TypeId::of::<T>() == TypeId::of::<Head>() { if TypeId::of::<T>() == TypeId::of::<Head>() {
@ -75,7 +129,7 @@ impl MatchType for () {
impl<Head, Tail> MatchType for (Head, Tail) impl<Head, Tail> MatchType for (Head, Tail)
where where
Head: 'static, Head: 'static,
Tail: TupleList + MatchType, Tail: MatchType,
{ {
fn match_type<T: 'static>(&self, f: fn(t: &T)) { fn match_type<T: 'static>(&self, f: fn(t: &T)) {
if TypeId::of::<T>() == TypeId::of::<Head>() { if TypeId::of::<T>() == TypeId::of::<Head>() {
@ -98,6 +152,30 @@ pub trait Named {
fn name(&self) -> &str; 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 { pub trait MatchNameAndType {
fn match_name_type<T: 'static>(&self, name: &str) -> Option<&T>; 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>; 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) impl<Head, Tail> MatchNameAndType for (Head, Tail)
where where
Head: 'static + Named, Head: 'static + Named,
Tail: TupleList + MatchNameAndType, Tail: MatchNameAndType,
{ {
fn match_name_type<T: 'static>(&self, name: &str) -> Option<&T> { fn match_name_type<T: 'static>(&self, name: &str) -> Option<&T> {
if TypeId::of::<T>() == TypeId::of::<Head>() && name == self.0.name() { if TypeId::of::<T>() == TypeId::of::<Head>() && name == self.0.name() {

View File

@ -150,6 +150,8 @@ where
C: Corpus<I>, C: Corpus<I>,
R: Rand, 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> { pub fn update_score(&self, state: &mut S, idx: usize) -> Result<(), Error> {
// Create a new top rated meta if not existing // Create a new top rated meta if not existing
if state.metadata().get::<TopRatedsMetadata>().is_none() { if state.metadata().get::<TopRatedsMetadata>().is_none() {
@ -194,16 +196,18 @@ where
Ok(()) Ok(())
} }
/// Cull the `Corpus` using the `MinimizerCorpusScheduler`
#[allow(clippy::unused_self)]
pub fn cull(&self, state: &mut S) -> Result<(), Error> { pub fn cull(&self, state: &mut S) -> Result<(), Error> {
if state.metadata().get::<TopRatedsMetadata>().is_none() { let top_rated = match state.metadata().get::<TopRatedsMetadata>() {
return Ok(()); None => return Ok(()),
} Some(val) => val,
let mut acc = HashSet::new(); };
let top_rated = state.metadata().get::<TopRatedsMetadata>().unwrap();
for key in top_rated.map.keys() { let mut acc = HashSet::new();
for (key, idx) in &top_rated.map {
if !acc.contains(key) { if !acc.contains(key) {
let idx = top_rated.map.get(key).unwrap();
let mut entry = state.corpus().get(*idx)?.borrow_mut(); let mut entry = state.corpus().get(*idx)?.borrow_mut();
let meta = entry.metadata().get::<M>().ok_or_else(|| { let meta = entry.metadata().get::<M>().ok_or_else(|| {
Error::KeyNotFound(format!( Error::KeyNotFound(format!(

View File

@ -5,9 +5,21 @@ use core::cell::RefCell;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "std")] #[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. /// A corpus able to store testcases to disk, and load them from disk, when they are being used.
#[cfg(feature = "std")] #[cfg(feature = "std")]
@ -20,6 +32,7 @@ where
entries: Vec<RefCell<Testcase<I>>>, entries: Vec<RefCell<Testcase<I>>>,
current: Option<usize>, current: Option<usize>,
dir_path: PathBuf, dir_path: PathBuf,
meta_format: Option<OnDiskMetadataFormat>,
} }
impl<I> Corpus<I> for OnDiskCorpus<I> impl<I> Corpus<I> for OnDiskCorpus<I>
@ -41,6 +54,17 @@ where
let filename_str = filename.to_str().expect("Invalid Path"); let filename_str = filename.to_str().expect("Invalid Path");
testcase.set_filename(filename_str.into()); 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 testcase
.store_input() .store_input()
.expect("Could not save testcase to disk"); .expect("Could not save testcase to disk");
@ -99,6 +123,22 @@ where
entries: vec![], entries: vec![],
current: None, current: None,
dir_path, 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,
}) })
} }
} }

View File

@ -217,14 +217,15 @@ where
self.cached_len = Some(l); self.cached_len = Some(l);
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(); let l = self.load_input()?.len();
self.cached_len = Some(l); self.cached_len = Some(l);
l l
} }
}, }
}) })
} }
} }

View File

@ -1,3 +1,5 @@
//! LLMP-backed event manager for scalable multi-processed fuzzing
use alloc::{string::ToString, vec::Vec}; use alloc::{string::ToString, vec::Vec};
use core::{marker::PhantomData, time::Duration}; use core::{marker::PhantomData, time::Duration};
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
@ -6,20 +8,15 @@ use serde::{de::DeserializeOwned, Serialize};
use core::ptr::read_volatile; use core::ptr::read_volatile;
#[cfg(feature = "std")] #[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::{ use crate::{
bolts::{ bolts::{
llmp::{self, LlmpClient, LlmpClientDescription, LlmpSender, Tag}, llmp::{self, Flag, LlmpClientDescription, LlmpSender, Tag},
shmem::{HasFd, ShMem}, shmem::ShMemProvider,
}, },
corpus::CorpusScheduler, corpus::CorpusScheduler,
events::{BrokerEventResult, Event, EventManager}, events::{BrokerEventResult, Event, EventManager},
@ -32,6 +29,21 @@ use crate::{
Error, 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 /// Forward this to the client
const _LLMP_TAG_EVENT_TO_CLIENT: llmp::Tag = 0x2C11E471; const _LLMP_TAG_EVENT_TO_CLIENT: llmp::Tag = 0x2C11E471;
/// Only handle this in the broker /// Only handle this in the broker
@ -39,57 +51,35 @@ const _LLMP_TAG_EVENT_TO_BROKER: llmp::Tag = 0x2B80438;
/// Handle in both /// Handle in both
/// ///
const LLMP_TAG_EVENT_TO_BOTH: llmp::Tag = 0x2B0741; const LLMP_TAG_EVENT_TO_BOTH: llmp::Tag = 0x2B0741;
const _LLMP_TAG_RESTART: llmp::Tag = 0x8357A87; const _LLMP_TAG_RESTART: llmp::Tag = 0x8357A87;
const _LLMP_TAG_NO_RESTART: llmp::Tag = 0x57A7EE71; const _LLMP_TAG_NO_RESTART: llmp::Tag = 0x57A7EE71;
#[derive(Clone, Debug)] #[derive(Debug)]
pub struct LlmpEventManager<I, S, SH, ST> pub struct LlmpEventManager<I, S, SP, ST>
where where
I: Input, I: Input,
S: IfInteresting<I>, S: IfInteresting<I>,
SH: ShMem, SP: ShMemProvider + 'static,
ST: Stats, ST: Stats,
//CE: CustomEvent<I>, //CE: CustomEvent<I>,
{ {
stats: Option<ST>, stats: Option<ST>,
llmp: llmp::LlmpConnection<SH>, llmp: llmp::LlmpConnection<SP>,
#[cfg(feature = "llmp_compression")]
compressor: GzipCompressor,
phantom: PhantomData<(I, S)>, phantom: PhantomData<(I, S)>,
} }
#[cfg(feature = "std")] /// The minimum buffer size at which to compress LLMP IPC messages.
#[cfg(unix)] #[cfg(feature = "llmp_compression")]
impl<I, S, ST> LlmpEventManager<I, S, UnixShMem, ST> const COMPRESS_THRESHOLD: usize = 1024;
impl<I, S, SP, ST> Drop for LlmpEventManager<I, S, SP, ST>
where where
I: Input, I: Input,
S: IfInteresting<I>, S: IfInteresting<I>,
ST: Stats, SP: ShMemProvider,
{
/// 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,
ST: Stats, ST: Stats,
{ {
/// LLMP clients will have to wait until their pages are mapped by somebody. /// 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 where
I: Input, I: Input,
S: IfInteresting<I>, S: IfInteresting<I>,
SH: ShMem, SP: ShMemProvider,
ST: Stats, ST: Stats,
{ {
/// Create llmp on a port /// Create llmp on a port
/// If the port is not yet bound, it will act as broker /// If the port is not yet bound, it will act as broker
/// Else, it will act as client. /// Else, it will act as client.
#[cfg(feature = "std")] #[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 { Ok(Self {
stats: Some(stats), 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, phantom: PhantomData,
}) })
} }
/// If a client respawns, it may reuse the existing connection, previously stored by LlmpClient::to_env /// If a client respawns, it may reuse the existing connection, previously stored by LlmpClient::to_env
#[cfg(feature = "std")] #[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 { Ok(Self {
stats: None, stats: None,
llmp: llmp::LlmpConnection::IsClient { 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. // Inserting a nop-stats element here so rust won't complain.
// In any case, the client won't currently use it. // In any case, the client won't currently use it.
phantom: PhantomData, phantom: PhantomData,
@ -138,26 +132,23 @@ where
/// Create an existing client from description /// Create an existing client from description
pub fn existing_client_from_description( pub fn existing_client_from_description(
shmem_provider: SP,
description: &LlmpClientDescription, description: &LlmpClientDescription,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
Ok(Self { Ok(Self {
stats: None, 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. // Inserting a nop-stats element here so rust won't complain.
// In any case, the client won't currently use it. // In any case, the client won't currently use it.
phantom: PhantomData, 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 /// Write the config for a client eventmgr to env vars, a new client can reattach using existing_client_from_env
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn to_env(&self, env_name: &str) { pub fn to_env(&self, env_name: &str) {
@ -179,10 +170,24 @@ where
match &mut self.llmp { match &mut self.llmp {
llmp::LlmpConnection::IsBroker { broker } => { llmp::LlmpConnection::IsBroker { broker } => {
let stats = self.stats.as_mut().unwrap(); let stats = self.stats.as_mut().unwrap();
#[cfg(feature = "llmp_compression")]
let compressor = &self.compressor;
broker.loop_forever( 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 { 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)? { match Self::handle_in_broker(stats, sender_id, &event)? {
BrokerEventResult::Forward => { BrokerEventResult::Forward => {
Ok(llmp::LlmpMsgHookResult::ForwardToClients) Ok(llmp::LlmpMsgHookResult::ForwardToClients)
@ -205,6 +210,7 @@ where
} }
/// Handle arriving events in the broker /// Handle arriving events in the broker
#[allow(clippy::unnecessary_wraps)]
fn handle_in_broker( fn handle_in_broker(
stats: &mut ST, stats: &mut ST,
sender_id: u32, sender_id: u32,
@ -256,6 +262,7 @@ where
} }
// Handle arriving events in the client // Handle arriving events in the client
#[allow(clippy::unused_self)]
fn handle_in_client<CS, E, OT>( fn handle_in_client<CS, E, OT>(
&mut self, &mut self,
state: &mut S, state: &mut S,
@ -285,7 +292,7 @@ where
let observers: OT = postcard::from_bytes(&observers_buf)?; let observers: OT = postcard::from_bytes(&observers_buf)?;
// TODO include ExitKind in NewTestcase // 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 if fitness > 0
&& state && state
.add_if_interesting(&input, fitness, scheduler)? .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 where
I: Input, I: Input,
S: IfInteresting<I>, S: IfInteresting<I>,
SH: ShMem + HasFd, SP: ShMemProvider,
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,
ST: Stats, //CE: CustomEvent<I>, ST: Stats, //CE: CustomEvent<I>,
{ {
/// The llmp client needs to wait until a broker mapped all pages, before shutting down. /// The llmp client needs to wait until a broker mapped all pages, before shutting down.
@ -352,11 +342,22 @@ where
let mut events = vec![]; let mut events = vec![];
match &mut self.llmp { match &mut self.llmp {
llmp::LlmpConnection::IsClient { client } => { 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 { if tag == _LLMP_TAG_EVENT_TO_BROKER {
panic!("EVENT_TO_BROKER parcel should not have arrived in the client!"); 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)); events.push((sender_id, event));
} }
} }
@ -372,6 +373,27 @@ where
Ok(count) 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> { fn fire(&mut self, _state: &mut S, event: Event<I>) -> Result<(), Error> {
let serialized = postcard::to_allocvec(&event)?; let serialized = postcard::to_allocvec(&event)?;
self.llmp.send_buf(LLMP_TAG_EVENT_TO_BOTH, &serialized)?; 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. /// Serialize the current state and corpus during an executiont to bytes.
/// On top, add the current llmp event manager instance to be restored /// 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. /// 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, state: &S,
mgr: &LlmpEventManager<I, S, SH, ST>, mgr: &LlmpEventManager<I, S, SP, ST>,
) -> Result<Vec<u8>, Error> ) -> Result<Vec<u8>, Error>
where where
I: Input, I: Input,
S: Serialize + IfInteresting<I>, S: Serialize + IfInteresting<I>,
SH: ShMem, SP: ShMemProvider,
ST: Stats, ST: Stats,
{ {
Ok(postcard::to_allocvec(&(&state, &mgr.describe()?))?) Ok(postcard::to_allocvec(&(&state, &mgr.describe()?))?)
} }
/// Deserialize the state and corpus tuple, previously serialized with `serialize_state_corpus(...)` /// 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], state_corpus_serialized: &[u8],
) -> Result<(S, LlmpEventManager<I, S, SH, ST>), Error> ) -> Result<(S, LlmpEventManager<I, S, SP, ST>), Error>
where where
I: Input, I: Input,
S: DeserializeOwned + IfInteresting<I>, S: DeserializeOwned + IfInteresting<I>,
SH: ShMem, SP: ShMemProvider,
ST: Stats, ST: Stats,
{ {
let tuple: (S, _) = postcard::from_bytes(&state_corpus_serialized)?; let tuple: (S, _) = postcard::from_bytes(&state_corpus_serialized)?;
Ok(( Ok((
tuple.0, 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`) /// A manager that can restart on the fly, storing states in-between (in `on_resatrt`)
#[derive(Clone, Debug)] #[derive(Debug)]
pub struct LlmpRestartingEventManager<I, S, SH, ST> pub struct LlmpRestartingEventManager<I, S, SP, ST>
where where
I: Input, I: Input,
S: IfInteresting<I>, S: IfInteresting<I>,
SH: ShMem, SP: ShMemProvider + 'static,
ST: Stats, ST: Stats,
//CE: CustomEvent<I>, //CE: CustomEvent<I>,
{ {
/// The embedded llmp event manager /// 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 /// 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 where
I: Input, I: Input,
S: IfInteresting<I> + Serialize, S: IfInteresting<I> + Serialize,
SH: ShMem, SP: ShMemProvider,
ST: Stats, //CE: CustomEvent<I>, ST: Stats, //CE: CustomEvent<I>,
{ {
/// The llmp client needs to wait until a broker mapped all pages, before shutting down. /// The llmp client needs to wait until a broker mapped all pages, before shutting down.
@ -477,72 +501,95 @@ 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) /// 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"; 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 where
I: Input, I: Input,
S: IfInteresting<I>, S: IfInteresting<I>,
SH: ShMem, SP: ShMemProvider,
ST: Stats, //CE: CustomEvent<I>, ST: Stats, //CE: CustomEvent<I>,
{ {
/// Create a new runner, the executed child doing the actual fuzzing. /// 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 } Self { llmp_mgr, sender }
} }
/// Get the sender /// Get the sender
pub fn sender(&self) -> &LlmpSender<SH> { pub fn sender(&self) -> &LlmpSender<SP> {
&self.sender &self.sender
} }
/// Get the sender (mut) /// 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 &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`. /// 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. /// The restarter will start a new process each time the child crashes or timeouts.
#[cfg(feature = "std")] #[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>, //mgr: &mut LlmpEventManager<I, S, SH, ST>,
stats: ST, stats: ST,
broker_port: u16, broker_port: u16,
) -> Result<(Option<S>, LlmpRestartingEventManager<I, S, SH, ST>), Error> ) -> Result<(Option<S>, LlmpRestartingEventManager<I, S, SP, ST>), Error>
where where
I: Input, I: Input,
S: DeserializeOwned + IfInteresting<I>, S: DeserializeOwned + IfInteresting<I>,
SH: ShMem + HasFd, // Todo: HasFd is only needed for Android SP: ShMemProvider,
ST: Stats, 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 // We start ourself as child process to actually fuzz
let (sender, mut receiver) = if std::env::var(_ENV_FUZZER_SENDER).is_err() { let (sender, mut receiver, mut new_shmem_provider) = if std::env::var(_ENV_FUZZER_SENDER)
#[cfg(target_os = "android")] .is_err()
{ {
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)?
};
if mgr.is_broker() { if mgr.is_broker() {
// Yep, broker. Just loop here. // Yep, broker. Just loop here.
println!("Doing broker things. Run this tool again to start fuzzing in a client."); println!("Doing broker things. Run this tool again to start fuzzing in a client.");
mgr.broker_loop()?; mgr.broker_loop()?;
return Err(Error::ShuttingDown); return Err(Error::ShuttingDown);
} else { }
// We are the fuzzer respawner in a llmp client // We are the fuzzer respawner in a llmp client
mgr.to_env(_ENV_FUZZER_BROKER_CLIENT_INITIAL); 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. // First, create a channel from the fuzzer (sender) to us (receiver) to report its state for restarts.
let sender = LlmpSender::new(0, false)?; let sender = { LlmpSender::new(shmem_provider.clone(), 0, false)? };
let receiver = LlmpReceiver::on_existing_map(
SH::clone_ref(&sender.out_maps.last().unwrap().shmem)?, let map = { shmem_provider.clone_ref(&sender.out_maps.last().unwrap().shmem)? };
None, let receiver = LlmpReceiver::on_existing_map(shmem_provider.clone(), map, None)?;
)?;
// Store the information to a map. // Store the information to a map.
sender.to_env(_ENV_FUZZER_SENDER)?; sender.to_env(_ENV_FUZZER_SENDER)?;
receiver.to_env(_ENV_FUZZER_RECEIVER)?; receiver.to_env(_ENV_FUZZER_RECEIVER)?;
@ -554,40 +601,57 @@ where
// On Unix, we fork (todo: measure if that is actually faster.) // On Unix, we fork (todo: measure if that is actually faster.)
#[cfg(unix)] #[cfg(unix)]
let _ = match unsafe { fork() }? { let child_status = match unsafe { fork() }? {
ForkResult::Parent(handle) => handle.status(), ForkResult::Parent(handle) => handle.status(),
ForkResult::Child => break (sender, receiver), ForkResult::Child => break (sender, receiver, shmem_provider),
}; };
// On windows, we spawn ourself again // On windows, we spawn ourself again
#[cfg(windows)] #[cfg(windows)]
startable_self()?.status()?; let child_status = startable_self()?.status()?;
if unsafe { read_volatile(&(*receiver.current_recv_map.page()).size_used) } == 0 { if unsafe { read_volatile(&(*receiver.current_recv_map.page()).size_used) } == 0 {
#[cfg(unix)]
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).");
}
// Storing state in the last round did not work // 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!"); 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); ctr = ctr.wrapping_add(1);
} }
}
} else { } else {
// We are the newly started fuzzing instance, first, connect to our own restore map. // We are the newly started fuzzing instance, first, connect to our own restore map.
// A sender and a receiver for single communication // 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)?, LlmpSender::on_existing_from_env(shmem_provider.clone(), _ENV_FUZZER_SENDER)?,
LlmpReceiver::<SH>::on_existing_from_env(_ENV_FUZZER_RECEIVER)?, 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 :)"); 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. // If we're restarting, deserialize the old state.
let (state, mut mgr) = match receiver.recv_buf()? { let (state, mut mgr) = match receiver.recv_buf()? {
None => { None => {
println!("First run. Let's set it all up"); println!("First run. Let's set it all up");
// Mgr to send and receive msgs from/to all other fuzzer instances // 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, _ENV_FUZZER_BROKER_CLIENT_INITIAL,
)?; )?;
@ -596,7 +660,8 @@ where
// Restoring from a previous run, deserialize state and corpus. // Restoring from a previous run, deserialize state and corpus.
Some((_sender, _tag, msg)) => { Some((_sender, _tag, msg)) => {
println!("Subsequent run. Let's load all data from shmem (received {} bytes from previous instance)", msg.len()); 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)) (Some(state), LlmpRestartingEventManager::new(mgr, sender))
} }

View File

@ -246,7 +246,7 @@ mod tests {
#[test] #[test]
fn test_event_serde() { 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 map = tuple_list!(obv);
let observers_buf = postcard::to_allocvec(&map).unwrap(); let observers_buf = postcard::to_allocvec(&map).unwrap();

View File

@ -73,6 +73,7 @@ where
} }
// Handle arriving events in the broker // Handle arriving events in the broker
#[allow(clippy::unnecessary_wraps)]
fn handle_in_broker(stats: &mut ST, event: &Event<I>) -> Result<BrokerEventResult, Error> { fn handle_in_broker(stats: &mut ST, event: &Event<I>) -> Result<BrokerEventResult, Error> {
match event { match event {
Event::NewTestcase { Event::NewTestcase {
@ -83,8 +84,12 @@ where
time, time,
executions, executions,
} => { } => {
stats.client_stats_mut()[0].update_corpus_size(*corpus_size as u64); stats
stats.client_stats_mut()[0].update_executions(*executions as u64, *time); .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()); stats.display(event.name().to_string());
Ok(BrokerEventResult::Handled) Ok(BrokerEventResult::Handled)
} }
@ -94,12 +99,16 @@ where
phantom: _, phantom: _,
} => { } => {
// TODO: The stats buffer should be added on client add. // 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()); stats.display(event.name().to_string());
Ok(BrokerEventResult::Handled) Ok(BrokerEventResult::Handled)
} }
Event::Objective { objective_size } => { 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()); stats.display(event.name().to_string());
Ok(BrokerEventResult::Handled) Ok(BrokerEventResult::Handled)
} }
@ -117,6 +126,7 @@ where
} }
// Handle arriving events in the client // 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> { fn handle_in_client(&mut self, _state: &mut S, event: Event<I>) -> Result<(), Error> {
Err(Error::Unknown(format!( Err(Error::Unknown(format!(
"Received illegal message that message should not have arrived: {:?}.", "Received illegal message that message should not have arrived: {:?}.",

View File

@ -25,26 +25,25 @@ use crate::{
Error, Error,
}; };
/// The inmem executor harness
type HarnessFunction<E> = fn(&E, &[u8]) -> ExitKind;
/// The inmem executor simply calls a target function, then returns afterwards. /// The inmem executor simply calls a target function, then returns afterwards.
pub struct InProcessExecutor<I, OT> pub struct InProcessExecutor<'a, H, I, OT>
where where
H: FnMut(&[u8]) -> ExitKind,
I: Input + HasTargetBytes, I: Input + HasTargetBytes,
OT: ObserversTuple, OT: ObserversTuple,
{ {
/// The name of this executor instance, to address it from other components /// The name of this executor instance, to address it from other components
name: &'static str, name: &'static str,
/// The harness function, being executed for each fuzzing loop execution /// The harness function, being executed for each fuzzing loop execution
harness_fn: HarnessFunction<Self>, harness_fn: &'a mut H,
/// The observers, observing each run /// The observers, observing each run
observers: OT, observers: OT,
phantom: PhantomData<I>, 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 where
H: FnMut(&[u8]) -> ExitKind,
I: Input + HasTargetBytes, I: Input + HasTargetBytes,
OT: ObserversTuple, OT: ObserversTuple,
{ {
@ -95,7 +94,7 @@ where
#[inline] #[inline]
fn run_target(&mut self, input: &I) -> Result<ExitKind, Error> { fn run_target(&mut self, input: &I) -> Result<ExitKind, Error> {
let bytes = input.target_bytes(); let bytes = input.target_bytes();
let ret = (self.harness_fn)(self, bytes.as_slice()); let ret = (self.harness_fn)(bytes.as_slice());
Ok(ret) 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 where
H: FnMut(&[u8]) -> ExitKind,
I: Input + HasTargetBytes, I: Input + HasTargetBytes,
OT: ObserversTuple, 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 where
H: FnMut(&[u8]) -> ExitKind,
I: Input + HasTargetBytes, I: Input + HasTargetBytes,
OT: ObserversTuple, OT: ObserversTuple,
{ {
@ -152,8 +153,9 @@ where
} }
} }
impl<I, OT> InProcessExecutor<I, OT> impl<'a, H, I, OT> InProcessExecutor<'a, H, I, OT>
where where
H: FnMut(&[u8]) -> ExitKind,
I: Input + HasTargetBytes, I: Input + HasTargetBytes,
OT: ObserversTuple, OT: ObserversTuple,
{ {
@ -166,7 +168,7 @@ where
/// This may return an error on unix, if signal handler setup fails /// This may return an error on unix, if signal handler setup fails
pub fn new<EM, OC, OFT, S>( pub fn new<EM, OC, OFT, S>(
name: &'static str, name: &'static str,
harness_fn: HarnessFunction<Self>, harness_fn: &'a mut H,
observers: OT, observers: OT,
_state: &mut S, _state: &mut S,
_event_mgr: &mut EM, _event_mgr: &mut EM,
@ -215,13 +217,25 @@ where
phantom: PhantomData, 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)] #[cfg(unix)]
mod unix_signal_handler { mod unix_signal_handler {
use alloc::vec::Vec; use alloc::vec::Vec;
use core::ptr; use core::ptr;
use libc::{c_void, siginfo_t}; use libc::{c_void, siginfo_t, ucontext_t};
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::io::{stdout, Write}; use std::io::{stdout, Write};
@ -259,8 +273,8 @@ mod unix_signal_handler {
pub event_mgr_ptr: *mut c_void, pub event_mgr_ptr: *mut c_void,
pub observers_ptr: *const c_void, pub observers_ptr: *const c_void,
pub current_input_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 crash_handler: unsafe fn(Signal, siginfo_t, &mut ucontext_t, data: &mut Self),
pub timeout_handler: unsafe fn(Signal, siginfo_t, c_void, data: &mut Self), pub timeout_handler: unsafe fn(Signal, siginfo_t, &mut ucontext_t, data: &mut Self),
} }
unsafe impl Send for InProcessExecutorHandlerData {} unsafe impl Send for InProcessExecutorHandlerData {}
@ -269,21 +283,21 @@ mod unix_signal_handler {
unsafe fn nop_handler( unsafe fn nop_handler(
_signal: Signal, _signal: Signal,
_info: siginfo_t, _info: siginfo_t,
_void: c_void, _context: &mut ucontext_t,
_data: &mut InProcessExecutorHandlerData, _data: &mut InProcessExecutorHandlerData,
) { ) {
} }
#[cfg(unix)] #[cfg(unix)]
impl Handler for InProcessExecutorHandlerData { 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 { unsafe {
let data = &mut GLOBAL_STATE; let data = &mut GLOBAL_STATE;
match signal { match signal {
Signal::SigUser2 | Signal::SigAlarm => { 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::SigFloatingPointException,
Signal::SigIllegalInstruction, Signal::SigIllegalInstruction,
Signal::SigSegmentationFault, Signal::SigSegmentationFault,
Signal::SigTrap,
] ]
} }
} }
@ -306,7 +321,7 @@ mod unix_signal_handler {
pub unsafe fn inproc_timeout_handler<EM, I, OC, OFT, OT, S>( pub unsafe fn inproc_timeout_handler<EM, I, OC, OFT, OT, S>(
_signal: Signal, _signal: Signal,
_info: siginfo_t, _info: siginfo_t,
_void: c_void, _context: &mut ucontext_t,
data: &mut InProcessExecutorHandlerData, data: &mut InProcessExecutorHandlerData,
) where ) where
EM: EventManager<I, S>, EM: EventManager<I, S>,
@ -334,7 +349,7 @@ mod unix_signal_handler {
let obj_fitness = state let obj_fitness = state
.objectives_mut() .objectives_mut()
.is_interesting_all(&input, observers, ExitKind::Timeout) .is_interesting_all(&input, observers, &ExitKind::Timeout)
.expect("In timeout handler objectives failure."); .expect("In timeout handler objectives failure.");
if obj_fitness > 0 { if obj_fitness > 0 {
state state
@ -368,7 +383,7 @@ mod unix_signal_handler {
pub unsafe fn inproc_crash_handler<EM, I, OC, OFT, OT, S>( pub unsafe fn inproc_crash_handler<EM, I, OC, OFT, OT, S>(
_signal: Signal, _signal: Signal,
_info: siginfo_t, _info: siginfo_t,
_void: c_void, _context: &mut ucontext_t,
data: &mut InProcessExecutorHandlerData, data: &mut InProcessExecutorHandlerData,
) where ) where
EM: EventManager<I, S>, EM: EventManager<I, S>,
@ -378,6 +393,10 @@ mod unix_signal_handler {
S: HasObjectives<OFT, I> + HasSolutions<OC, I>, S: HasObjectives<OFT, I> + HasSolutions<OC, I>,
I: Input + HasTargetBytes, 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")] #[cfg(feature = "std")]
println!("Crashed with {}", _signal); println!("Crashed with {}", _signal);
if !data.current_input_ptr.is_null() { if !data.current_input_ptr.is_null() {
@ -387,6 +406,45 @@ mod unix_signal_handler {
#[cfg(feature = "std")] #[cfg(feature = "std")]
println!("Child crashed!"); 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")] #[cfg(feature = "std")]
let _ = stdout().flush(); let _ = stdout().flush();
@ -396,7 +454,7 @@ mod unix_signal_handler {
let obj_fitness = state let obj_fitness = state
.objectives_mut() .objectives_mut()
.is_interesting_all(&input, observers, ExitKind::Crash) .is_interesting_all(&input, observers, &ExitKind::Crash)
.expect("In crash handler objectives failure."); .expect("In crash handler objectives failure.");
if obj_fitness > 0 { if obj_fitness > 0 {
let new_input = input.clone(); let new_input = input.clone();
@ -560,7 +618,7 @@ mod windows_exception_handler {
let obj_fitness = state let obj_fitness = state
.objectives_mut() .objectives_mut()
.is_interesting_all(&input, observers, ExitKind::Crash) .is_interesting_all(&input, observers, &ExitKind::Crash)
.expect("In crash handler objectives failure."); .expect("In crash handler objectives failure.");
if obj_fitness > 0 { if obj_fitness > 0 {
let new_input = input.clone(); let new_input = input.clone();
@ -627,19 +685,15 @@ mod tests {
use crate::{ use crate::{
bolts::tuples::tuple_list, bolts::tuples::tuple_list,
executors::{Executor, ExitKind, InProcessExecutor}, 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] #[test]
fn test_inmem_exec() { fn test_inmem_exec() {
use crate::inputs::NopInput; let mut harness = |_buf: &[u8]| ExitKind::Ok;
let mut in_process_executor = InProcessExecutor::<NopInput, ()> { let mut in_process_executor = InProcessExecutor::<_, NopInput, ()> {
harness_fn: test_harness_fn_nop, harness_fn: &mut harness,
observers: tuple_list!(), observers: tuple_list!(),
name: "main", name: "main",
phantom: PhantomData, phantom: PhantomData,

View File

@ -4,27 +4,29 @@ pub mod inprocess;
pub use inprocess::InProcessExecutor; pub use inprocess::InProcessExecutor;
pub mod timeout; pub mod timeout;
pub use timeout::TimeoutExecutor; pub use timeout::TimeoutExecutor;
#[cfg(feature = "runtime")]
pub mod runtime;
use core::cmp::PartialEq;
use core::marker::PhantomData; use core::marker::PhantomData;
use crate::{ use crate::{
bolts::tuples::Named, bolts::{serdeany::SerdeAny, tuples::Named},
events::EventManager, events::EventManager,
inputs::{HasTargetBytes, Input}, inputs::{HasTargetBytes, Input},
observers::ObserversTuple, observers::ObserversTuple,
Error, Error,
}; };
use alloc::boxed::Box;
pub trait CustomExitKind: core::fmt::Debug + SerdeAny + 'static {}
/// How an execution finished. /// How an execution finished.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug)]
pub enum ExitKind { pub enum ExitKind {
Ok, Ok,
Crash, Crash,
OOM, Oom,
Timeout, Timeout,
Custom(Box<dyn CustomExitKind>),
} }
pub trait HasObservers<OT> pub trait HasObservers<OT>
@ -51,7 +53,7 @@ where
} }
/// A simple executor that does nothing. /// 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> { struct NopExecutor<I> {
phantom: PhantomData<I>, phantom: PhantomData<I>,
} }

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