Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reachability example #65

Merged
merged 14 commits into from
May 5, 2021
1 change: 1 addition & 0 deletions fuzzers/libfuzzer_reachability/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
libpng-*
30 changes: 30 additions & 0 deletions fuzzers/libfuzzer_reachability/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "libfuzzer_reachability"
version = "0.2.0"
authors = ["Andrea Fioraldi <[email protected]>", "Dominik Maier <[email protected]>"]
edition = "2018"

[features]
default = ["std"]
std = []

#[profile.release]
#lto = true
#codegen-units = 1
#opt-level = 3
#debug = true

[build-dependencies]
cc = { version = "1.0", features = ["parallel"] }
which = { version = "4.0.2" }
num_cpus = "1.0"

[dependencies]
libafl = { path = "../../libafl/" }
libafl_targets = { path = "../../libafl_targets/", features = ["pcguard_hitcounts", "libfuzzer"] }
# TODO Include it only when building cc
libafl_cc = { path = "../../libafl_cc/" }

[lib]
name = "libfuzzer_libpng"
crate-type = ["staticlib"]
79 changes: 79 additions & 0 deletions fuzzers/libfuzzer_reachability/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Libfuzzer for libpng

This folder contains an example fuzzer for libpng, using LLMP for fast multi-process fuzzing and crash detection.

In contrast to other fuzzer examples, this setup uses `fuzz_loop_for`, to occasionally respawn the fuzzer executor.
While this costs performance, it can be useful for targets with memory leaks or other instabilities.
If your target is really instable, however, consider exchanging the `InProcessExecutor` for a `ForkserverExecutor` instead.

To show off crash detection, we added a `ud2` instruction to the harness, edit harness.cc if you want a non-crashing example.
It has been tested on Linux.

## Build

To build this example, run

```bash
cargo build --release
```

This will build the library with the fuzzer (src/lib.rs) with the libfuzzer compatibility layer and the SanitizerCoverage runtime functions for coverage feedback.
In addition, it will also build two C and C++ compiler wrappers (bin/libafl_c(libafl_c/xx).rs) that you must use to compile the target.

The compiler wrappers, `libafl_cc` and libafl_cxx`, will end up in `./target/release/` (or `./target/debug`, in case you did not build with the `--release` flag).

Then download libpng, and unpack the archive:
```bash
wget https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz
tar -xvf libpng-1.6.37.tar.xz
```
Run `patch libpng-1.6.37/png.c diff.patch` before compiling the libpng
Now compile libpng, using the libafl_cc compiler wrapper:

```bash
cd libpng-1.6.37
./configure
make CC=$(realpath ../target/release/libafl_cc) CXX=$(realpath ../target/release/libafl_cxx) -j `nproc`
```

You can find the static lib at `libpng-1.6.37/.libs/libpng16.a`.

Now, we have to build the libfuzzer harness and link all together to create our fuzzer binary.

```
cd ..
./target/release/libafl_cxx ./harness.cc libpng-1.6.37/.libs/libpng16.a -I libpng-1.6.37/ -o fuzzer_libpng -lz -lm
```

Afterward, the fuzzer will be ready to run.
Note that, unless you use the `launcher`, you will have to run the binary multiple times to actually start the fuzz process, see `Run` in the following.
This allows you to run multiple different builds of the same fuzzer alongside, for example, with and without ASAN (`-fsanitize=address`) or with different mutators.

## Run

The first time you run the binary, the broker will open a tcp port (currently on port `1337`), waiting for fuzzer clients to connect. This port is local and only used for the initial handshake. All further communication happens via shared map, to be independent of the kernel. 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 >
```

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

Binary file added fuzzers/libfuzzer_reachability/corpus/not_kitty.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions fuzzers/libfuzzer_reachability/diff.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
15a16,19
> #define TARGET_SIZE 4
>
> size_t __lafl_dummy_list[TARGET_SIZE] = {0};
> size_t *__libafl_target_list = __lafl_dummy_list;
2562a2567
> __lafl_dummy_list[0] = 1;
2584a2590
> __lafl_dummy_list[1] = 1;
197 changes: 197 additions & 0 deletions fuzzers/libfuzzer_reachability/harness.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// libpng_read_fuzzer.cc
// Copyright 2017-2018 Glenn Randers-Pehrson
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that may
// be found in the LICENSE file https://cs.chromium.org/chromium/src/LICENSE

// Last changed in libpng 1.6.35 [July 15, 2018]

// The modifications in 2017 by Glenn Randers-Pehrson include
// 1. addition of a PNG_CLEANUP macro,
// 2. setting the option to ignore ADLER32 checksums,
// 3. adding "#include <string.h>" which is needed on some platforms
// to provide memcpy().
// 4. adding read_end_info() and creating an end_info structure.
// 5. adding calls to png_set_*() transforms commonly used by browsers.

#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include <vector>

#define PNG_INTERNAL
#include "png.h"

#define PNG_CLEANUP \
if(png_handler.png_ptr) \
{ \
if (png_handler.row_ptr) \
png_free(png_handler.png_ptr, png_handler.row_ptr); \
if (png_handler.end_info_ptr) \
png_destroy_read_struct(&png_handler.png_ptr, &png_handler.info_ptr,\
&png_handler.end_info_ptr); \
else if (png_handler.info_ptr) \
png_destroy_read_struct(&png_handler.png_ptr, &png_handler.info_ptr,\
nullptr); \
else \
png_destroy_read_struct(&png_handler.png_ptr, nullptr, nullptr); \
png_handler.png_ptr = nullptr; \
png_handler.row_ptr = nullptr; \
png_handler.info_ptr = nullptr; \
png_handler.end_info_ptr = nullptr; \
}

struct BufState {
const uint8_t* data;
size_t bytes_left;
};

struct PngObjectHandler {
png_infop info_ptr = nullptr;
png_structp png_ptr = nullptr;
png_infop end_info_ptr = nullptr;
png_voidp row_ptr = nullptr;
BufState* buf_state = nullptr;

~PngObjectHandler() {
if (row_ptr)
png_free(png_ptr, row_ptr);
if (end_info_ptr)
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info_ptr);
else if (info_ptr)
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
else
png_destroy_read_struct(&png_ptr, nullptr, nullptr);
delete buf_state;
}
};

void user_read_data(png_structp png_ptr, png_bytep data, size_t length) {
BufState* buf_state = static_cast<BufState*>(png_get_io_ptr(png_ptr));
if (length > buf_state->bytes_left) {
png_error(png_ptr, "read error");
}
memcpy(data, buf_state->data, length);
buf_state->bytes_left -= length;
buf_state->data += length;
}

static const int kPngHeaderSize = 8;

// Entry point for LibFuzzer.
// Roughly follows the libpng book example:
// http://www.libpng.org/pub/png/book/chapter13.html
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size < kPngHeaderSize) {
return 0;
}

std::vector<unsigned char> v(data, data + size);
if (png_sig_cmp(v.data(), 0, kPngHeaderSize)) {
// not a PNG.
return 0;
}

PngObjectHandler png_handler;
png_handler.png_ptr = nullptr;
png_handler.row_ptr = nullptr;
png_handler.info_ptr = nullptr;
png_handler.end_info_ptr = nullptr;

png_handler.png_ptr = png_create_read_struct
(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_handler.png_ptr) {
return 0;
}

png_handler.info_ptr = png_create_info_struct(png_handler.png_ptr);
if (!png_handler.info_ptr) {
PNG_CLEANUP
return 0;
}

png_handler.end_info_ptr = png_create_info_struct(png_handler.png_ptr);
if (!png_handler.end_info_ptr) {
PNG_CLEANUP
return 0;
}

png_set_crc_action(png_handler.png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
#ifdef PNG_IGNORE_ADLER32
png_set_option(png_handler.png_ptr, PNG_IGNORE_ADLER32, PNG_OPTION_ON);
#endif

// Setting up reading from buffer.
png_handler.buf_state = new BufState();
png_handler.buf_state->data = data + kPngHeaderSize;
png_handler.buf_state->bytes_left = size - kPngHeaderSize;
png_set_read_fn(png_handler.png_ptr, png_handler.buf_state, user_read_data);
png_set_sig_bytes(png_handler.png_ptr, kPngHeaderSize);

if (setjmp(png_jmpbuf(png_handler.png_ptr))) {
PNG_CLEANUP
return 0;
}

// Reading.
png_read_info(png_handler.png_ptr, png_handler.info_ptr);

// reset error handler to put png_deleter into scope.
if (setjmp(png_jmpbuf(png_handler.png_ptr))) {
PNG_CLEANUP
return 0;
}

png_uint_32 width, height;
int bit_depth, color_type, interlace_type, compression_type;
int filter_type;

if (!png_get_IHDR(png_handler.png_ptr, png_handler.info_ptr, &width,
&height, &bit_depth, &color_type, &interlace_type,
&compression_type, &filter_type)) {
PNG_CLEANUP
return 0;
}

// This is going to be too slow.
if (width && height > 100000000 / width) {
PNG_CLEANUP
#ifdef HAS_DUMMY_CRASH
#ifdef __aarch64__
asm volatile (".word 0xf7f0a000\n");
#else
asm("ud2");
#endif
#endif
return 0;
}

// Set several transforms that browsers typically use:
png_set_gray_to_rgb(png_handler.png_ptr);
png_set_expand(png_handler.png_ptr);
png_set_packing(png_handler.png_ptr);
png_set_scale_16(png_handler.png_ptr);
png_set_tRNS_to_alpha(png_handler.png_ptr);

int passes = png_set_interlace_handling(png_handler.png_ptr);

png_read_update_info(png_handler.png_ptr, png_handler.info_ptr);

png_handler.row_ptr = png_malloc(
png_handler.png_ptr, png_get_rowbytes(png_handler.png_ptr,
png_handler.info_ptr));

for (int pass = 0; pass < passes; ++pass) {
for (png_uint_32 y = 0; y < height; ++y) {
png_read_row(png_handler.png_ptr,
static_cast<png_bytep>(png_handler.row_ptr), nullptr);
}
}

png_read_end(png_handler.png_ptr, png_handler.end_info_ptr);

PNG_CLEANUP
return 0;
}

33 changes: 33 additions & 0 deletions fuzzers/libfuzzer_reachability/src/bin/libafl_cc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use libafl_cc::{ClangWrapper, CompilerWrapper, LIB_EXT, LIB_PREFIX};
use std::env;

fn main() {
let args: Vec<String> = env::args().collect();
if args.len() > 1 {
let mut dir = env::current_exe().unwrap();
dir.pop();

let mut cc = ClangWrapper::new("clang", "clang++");
cc.from_args(&args)
.unwrap()
.add_arg("-fsanitize-coverage=trace-pc-guard".into())
.unwrap()
.add_link_arg(
dir.join(format!("{}libfuzzer_libpng.{}", LIB_PREFIX, LIB_EXT))
.display()
.to_string(),
)
.unwrap();
// Libraries needed by libafl on Windows
#[cfg(windows)]
cc.add_link_arg("-lws2_32".into())
.unwrap()
.add_link_arg("-lBcrypt".into())
.unwrap()
.add_link_arg("-lAdvapi32".into())
.unwrap();
cc.run().unwrap();
} else {
panic!("LibAFL CC: No Arguments given");
}
}
34 changes: 34 additions & 0 deletions fuzzers/libfuzzer_reachability/src/bin/libafl_cxx.rs
Original file line number Diff line number Diff line change
@@ -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");
}
}
Loading