Libmozjpeg example added (#15)
* WIP Harness for libmozjpeg * Taskset removal (wrong invocation, without -c) * Clean up Fixed taskset in test.sh * Docs * Formatting * Formatting * Formatting * Formatting * jpeg example now uses a tokens file * fixed testcases * fixing build * fixed more bugs * metadatas->metadata * token files * added doctest test Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
parent
d0d9d2887f
commit
959c8f0dd8
4
.github/workflows/build_and_test.yml
vendored
4
.github/workflows/build_and_test.yml
vendored
@ -46,5 +46,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Test
|
- name: Build Docs
|
||||||
run: cargo doc
|
run: cargo doc
|
||||||
|
- name: Test Docs
|
||||||
|
run: cargo test --doc
|
@ -11,4 +11,5 @@ members = [
|
|||||||
|
|
||||||
#example fuzzers
|
#example fuzzers
|
||||||
"fuzzers/libfuzzer_libpng",
|
"fuzzers/libfuzzer_libpng",
|
||||||
|
"fuzzers/libfuzzer_libmozjpeg",
|
||||||
]
|
]
|
1
fuzzers/libfuzzer_libmozjpeg/.gitignore
vendored
Normal file
1
fuzzers/libfuzzer_libmozjpeg/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.tar.gz
|
31
fuzzers/libfuzzer_libmozjpeg/Cargo.toml
Normal file
31
fuzzers/libfuzzer_libmozjpeg/Cargo.toml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
[package]
|
||||||
|
name = "libfuzzer_libmozjpeg"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Marcin Kozlowski <marcinguy@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["std"]
|
||||||
|
std = []
|
||||||
|
|
||||||
|
#[profile.release]
|
||||||
|
#lto = true
|
||||||
|
#codegen-units = 1
|
||||||
|
#opt-level = 3
|
||||||
|
#debug = true
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cc = { version = "1.0", features = ["parallel"] }
|
||||||
|
num_cpus = "1.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libafl = { path = "../../libafl/" }
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "libfuzzer_libmozjpeg"
|
||||||
|
path = "./src/fuzzer.rs"
|
||||||
|
test = false
|
||||||
|
bench = false
|
28
fuzzers/libfuzzer_libmozjpeg/README.md
Normal file
28
fuzzers/libfuzzer_libmozjpeg/README.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Libfuzzer for libmozjpeg
|
||||||
|
|
||||||
|
This folder contains an example fuzzer for libmozjpeg, using LLMP for fast multi-process fuzzing and crash detection.
|
||||||
|
It has been tested on Linux.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
To build this example, run `cargo build --example libfuzzer_libmozjpeg --release`.
|
||||||
|
This will call (the build.rs)[./builld.rs], which in turn downloads a libmozjpeg archive from the web.
|
||||||
|
Then, it will link (the fuzzer)[./src/fuzzer.rs] against (the c++ harness)[./harness.cc] and the instrumented `libmozjpeg`.
|
||||||
|
Afterwards, the fuzzer will be ready to run, from `../../target/examples/libfuzzer_libmozjpeg`.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
For convenience, you may just run `./test.sh` in this folder or:
|
||||||
|
|
||||||
|
broker.sh - starts the broker
|
||||||
|
start.sh - starts as many clients as there are cores
|
||||||
|
stop.sh - stop everything
|
||||||
|
|
||||||
|
|
3
fuzzers/libfuzzer_libmozjpeg/broker.sh
Executable file
3
fuzzers/libfuzzer_libmozjpeg/broker.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
taskset -c 0 ./.libfuzzer_test.elf
|
||||||
|
|
112
fuzzers/libfuzzer_libmozjpeg/build.rs
Normal file
112
fuzzers/libfuzzer_libmozjpeg/build.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
// build.rs
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
const LIBMOZJPEG_URL: &str = "https://github.com/mozilla/mozjpeg/archive/v4.0.3.tar.gz";
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||||
|
let cwd = env::current_dir().unwrap().to_string_lossy().to_string();
|
||||||
|
let out_dir = out_dir.to_string_lossy().to_string();
|
||||||
|
let out_dir_path = Path::new(&out_dir);
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed=./runtime/rt.c",);
|
||||||
|
println!("cargo:rerun-if-changed=harness.cc");
|
||||||
|
|
||||||
|
let libmozjpeg = format!("{}/mozjpeg-4.0.3", &out_dir);
|
||||||
|
let libmozjpeg_path = Path::new(&libmozjpeg);
|
||||||
|
let libmozjpeg_tar = format!("{}/v4.0.3.tar.gz", &cwd);
|
||||||
|
|
||||||
|
// Enforce clang for its -fsanitize-coverage support.
|
||||||
|
std::env::set_var("CC", "clang");
|
||||||
|
std::env::set_var("CXX", "clang++");
|
||||||
|
|
||||||
|
if !libmozjpeg_path.is_dir() {
|
||||||
|
if !Path::new(&libmozjpeg_tar).is_file() {
|
||||||
|
println!("cargo:warning=Libmozjpeg not found, downloading...");
|
||||||
|
// Download libmozjpeg
|
||||||
|
Command::new("wget")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(LIBMOZJPEG_URL)
|
||||||
|
.arg("-O")
|
||||||
|
.arg(&libmozjpeg_tar)
|
||||||
|
.status()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
Command::new("tar")
|
||||||
|
.current_dir(&out_dir_path)
|
||||||
|
.arg("-xvf")
|
||||||
|
.arg(&libmozjpeg_tar)
|
||||||
|
.status()
|
||||||
|
.unwrap();
|
||||||
|
Command::new(format!("{}/cmake", &libmozjpeg))
|
||||||
|
.current_dir(&out_dir_path)
|
||||||
|
.args(&[
|
||||||
|
"-G\"Unix Makefiles\"",
|
||||||
|
"--disable-shared",
|
||||||
|
&libmozjpeg,
|
||||||
|
"CC=clang",
|
||||||
|
"CFLAGS=-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||||
|
"LDFLAGS=-g -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||||
|
])
|
||||||
|
.env("CC", "clang")
|
||||||
|
.env("CXX", "clang++")
|
||||||
|
.env(
|
||||||
|
"CFLAGS",
|
||||||
|
"-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||||
|
)
|
||||||
|
.env(
|
||||||
|
"CXXFLAGS",
|
||||||
|
"-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||||
|
)
|
||||||
|
.env("LDFLAGS", "-g -fPIE -fsanitize-coverage=trace-pc-guard");
|
||||||
|
Command::new("make")
|
||||||
|
.current_dir(&libmozjpeg_path)
|
||||||
|
//.arg(&format!("-j{}", num_cpus::get()))
|
||||||
|
.args(&[
|
||||||
|
"CC=clang",
|
||||||
|
"CXX=clang++",
|
||||||
|
"CFLAGS=-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||||
|
"LDFLAGS=-g -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||||
|
"CXXFLAGS=-D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||||
|
])
|
||||||
|
.env("CC", "clang")
|
||||||
|
.env("CXX", "clang++")
|
||||||
|
.env(
|
||||||
|
"CFLAGS",
|
||||||
|
"-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||||
|
)
|
||||||
|
.env(
|
||||||
|
"CXXFLAGS",
|
||||||
|
"-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
|
||||||
|
)
|
||||||
|
.env("LDFLAGS", "-g -fPIE -fsanitize-coverage=trace-pc-guard")
|
||||||
|
.status()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
cc::Build::new()
|
||||||
|
.file("../libfuzzer_runtime/rt.c")
|
||||||
|
.compile("libfuzzer-sys");
|
||||||
|
|
||||||
|
cc::Build::new()
|
||||||
|
.include(&libmozjpeg_path)
|
||||||
|
.flag("-fsanitize-coverage=trace-pc-guard")
|
||||||
|
.file("./harness.cc")
|
||||||
|
.compile("libfuzzer-harness");
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
BIN
fuzzers/libfuzzer_libmozjpeg/corpus/blank.jpg
Normal file
BIN
fuzzers/libfuzzer_libmozjpeg/corpus/blank.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 631 B |
92
fuzzers/libfuzzer_libmozjpeg/harness.cc
Normal file
92
fuzzers/libfuzzer_libmozjpeg/harness.cc
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include "jpeglib.h"
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
|
||||||
|
struct my_error_mgr {
|
||||||
|
struct jpeg_error_mgr pub; /* "public" fields */
|
||||||
|
|
||||||
|
jmp_buf setjmp_buffer; /* for return to caller */
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct my_error_mgr *my_error_ptr;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Here's the routine that will replace the standard error_exit method:
|
||||||
|
*/
|
||||||
|
|
||||||
|
METHODDEF(void)
|
||||||
|
my_error_exit(j_common_ptr cinfo)
|
||||||
|
{
|
||||||
|
/* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
|
||||||
|
my_error_ptr myerr = (my_error_ptr)cinfo->err;
|
||||||
|
|
||||||
|
/* Always display the message. */
|
||||||
|
/* We could postpone this until after returning, if we chose. */
|
||||||
|
(*cinfo->err->output_message) (cinfo);
|
||||||
|
|
||||||
|
/* Return control to the setjmp point */
|
||||||
|
longjmp(myerr->setjmp_buffer, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int do_read_JPEG_file(struct jpeg_decompress_struct *cinfo, const uint8_t *input, size_t len)
|
||||||
|
{
|
||||||
|
struct my_error_mgr jerr;
|
||||||
|
/* More stuff */
|
||||||
|
JSAMPARRAY buffer; /* Output row buffer */
|
||||||
|
int row_stride; /* physical row width in output buffer */
|
||||||
|
/* Step 1: allocate and initialize JPEG decompression object */
|
||||||
|
/* We set up the normal JPEG error routines, then override error_exit. */
|
||||||
|
cinfo->err = jpeg_std_error(&jerr.pub);
|
||||||
|
jerr.pub.error_exit = my_error_exit;
|
||||||
|
/* Establish the setjmp return context for my_error_exit to use. */
|
||||||
|
if (setjmp(jerr.setjmp_buffer)) {
|
||||||
|
jpeg_destroy_decompress(cinfo);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* Now we can initialize the JPEG decompression object. */
|
||||||
|
jpeg_create_decompress(cinfo);
|
||||||
|
/* Step 2: specify data source (eg, a file) */
|
||||||
|
jpeg_mem_src(cinfo,input, len );
|
||||||
|
/* Step 3: read file parameters with jpeg_read_header() */
|
||||||
|
(void)jpeg_read_header(cinfo, TRUE);
|
||||||
|
/* Step 4: set parameters for decompression */
|
||||||
|
/* In this example, we don't need to change any of the defaults set by
|
||||||
|
* jpeg_read_header(), so we do nothing here.
|
||||||
|
*/
|
||||||
|
/* Step 5: Start decompressor */
|
||||||
|
(void)jpeg_start_decompress(cinfo);
|
||||||
|
/* JSAMPLEs per row in output buffer */
|
||||||
|
row_stride = cinfo->output_width * cinfo->output_components;
|
||||||
|
/* Make a one-row-high sample array that will go away when done with image */
|
||||||
|
buffer = (*cinfo->mem->alloc_sarray)
|
||||||
|
((j_common_ptr)cinfo, JPOOL_IMAGE, row_stride, 1);
|
||||||
|
/* Step 6: while (scan lines remain to be read) */
|
||||||
|
/* jpeg_read_scanlines(...); */
|
||||||
|
while (cinfo->output_scanline < cinfo->output_height) {
|
||||||
|
(void)jpeg_read_scanlines(cinfo, buffer, 1);
|
||||||
|
/* Assume put_scanline_someplace wants a pointer and sample count. */
|
||||||
|
//put_scanline_someplace(buffer[0], row_stride);
|
||||||
|
}
|
||||||
|
/* Step 7: Finish decompression */
|
||||||
|
(void)jpeg_finish_decompress(cinfo);
|
||||||
|
/* Step 8: Release JPEG decompression object */
|
||||||
|
//jpeg_destroy_decompress(cinfo);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||||
|
|
||||||
|
|
||||||
|
struct jpeg_decompress_struct cinfo;
|
||||||
|
do_read_JPEG_file(&cinfo,data,size);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
22
fuzzers/libfuzzer_libmozjpeg/jpeg.tkns
Normal file
22
fuzzers/libfuzzer_libmozjpeg/jpeg.tkns
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#
|
||||||
|
# AFL dictionary for JPEG images
|
||||||
|
# ------------------------------
|
||||||
|
#
|
||||||
|
# Created by Michal Zalewski
|
||||||
|
#
|
||||||
|
|
||||||
|
header_jfif="JFIF\x00"
|
||||||
|
header_jfxx="JFXX\x00"
|
||||||
|
|
||||||
|
section_ffc0="\xff\xc0"
|
||||||
|
section_ffc2="\xff\xc2"
|
||||||
|
section_ffc4="\xff\xc4"
|
||||||
|
section_ffd0="\xff\xd0"
|
||||||
|
section_ffd8="\xff\xd8"
|
||||||
|
section_ffd9="\xff\xd9"
|
||||||
|
section_ffda="\xff\xda"
|
||||||
|
section_ffdb="\xff\xdb"
|
||||||
|
section_ffdd="\xff\xdd"
|
||||||
|
section_ffe0="\xff\xe0"
|
||||||
|
section_ffe1="\xff\xe1"
|
||||||
|
section_fffe="\xff\xfe"
|
152
fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs
Normal file
152
fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts
|
||||||
|
//! The example harness is built for libmozjpeg.
|
||||||
|
|
||||||
|
use std::{env, path::PathBuf};
|
||||||
|
|
||||||
|
use libafl::{
|
||||||
|
bolts::{shmem::UnixShMem, tuples::tuple_list},
|
||||||
|
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus, RandCorpusScheduler},
|
||||||
|
events::setup_restarting_mgr,
|
||||||
|
executors::{inprocess::InProcessExecutor, Executor, ExitKind},
|
||||||
|
feedbacks::{CrashFeedback, MaxMapFeedback},
|
||||||
|
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer},
|
||||||
|
inputs::Input,
|
||||||
|
mutators::scheduled::HavocBytesMutator,
|
||||||
|
mutators::token_mutations::Tokens,
|
||||||
|
observers::StdMapObserver,
|
||||||
|
stages::mutational::StdMutationalStage,
|
||||||
|
state::{HasCorpus, HasMetadata, State},
|
||||||
|
stats::SimpleStats,
|
||||||
|
utils::{current_nanos, StdRand},
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// We will interact with a C++ target, so use external c functionality
|
||||||
|
extern "C" {
|
||||||
|
/// int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
|
||||||
|
fn LLVMFuzzerTestOneInput(data: *const u8, size: usize) -> i32;
|
||||||
|
|
||||||
|
// afl_libfuzzer_init calls LLVMFUzzerInitialize()
|
||||||
|
fn afl_libfuzzer_init() -> i32;
|
||||||
|
|
||||||
|
static __lafl_edges_map: *mut u8;
|
||||||
|
static __lafl_cmp_map: *mut u8;
|
||||||
|
static __lafl_max_edges_size: u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The wrapped harness function, calling out to the LLVM-style harness
|
||||||
|
fn harness<E, I>(_executor: &E, buf: &[u8]) -> ExitKind
|
||||||
|
where
|
||||||
|
E: Executor<I>,
|
||||||
|
I: Input,
|
||||||
|
{
|
||||||
|
// println!("{:?}", buf);
|
||||||
|
unsafe {
|
||||||
|
LLVMFuzzerTestOneInput(buf.as_ptr(), buf.len());
|
||||||
|
}
|
||||||
|
ExitKind::Ok
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The main fn, usually parsing parameters, and starting the fuzzer
|
||||||
|
pub fn main() {
|
||||||
|
// Registry the metadata types used in this fuzzer
|
||||||
|
// Needed only on no_std
|
||||||
|
//RegistryBuilder::register::<Tokens>();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Workdir: {:?}",
|
||||||
|
env::current_dir().unwrap().to_string_lossy().to_string()
|
||||||
|
);
|
||||||
|
fuzz(
|
||||||
|
vec![PathBuf::from("./corpus")],
|
||||||
|
PathBuf::from("./crashes"),
|
||||||
|
1337,
|
||||||
|
)
|
||||||
|
.expect("An error occurred while fuzzing");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The actual fuzzer
|
||||||
|
fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> {
|
||||||
|
// 'While the stats are state, they are usually used in the broker - which is likely never restarted
|
||||||
|
let stats = SimpleStats::new(|s| println!("{}", s));
|
||||||
|
|
||||||
|
// The restarting state will spawn the same process again as child, then restarted it each time it crashes.
|
||||||
|
let (state, mut restarting_mgr) =
|
||||||
|
setup_restarting_mgr::<_, _, UnixShMem, _>(stats, broker_port)
|
||||||
|
.expect("Failed to setup the restarter".into());
|
||||||
|
|
||||||
|
// Create an observation channel using the coverage map
|
||||||
|
let edges_observer =
|
||||||
|
StdMapObserver::new_from_ptr("edges", unsafe { __lafl_edges_map }, unsafe {
|
||||||
|
__lafl_max_edges_size as usize
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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(&edges_observer)),
|
||||||
|
// Corpus in which we store solutions (crashes in this example),
|
||||||
|
// on disk so the user can get them after stopping the fuzzer
|
||||||
|
OnDiskCorpus::new(objective_dir).unwrap(),
|
||||||
|
// Feedbacks to recognize an input as solution
|
||||||
|
tuple_list!(CrashFeedback::new()),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("We're a client, let's fuzz :)");
|
||||||
|
|
||||||
|
// Add the JPEG tokens if not existing
|
||||||
|
if state.metadata().get::<Tokens>().is_none() {
|
||||||
|
state.add_metadata(Tokens::from_tokens_file("./jpeg.tkns")?);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup a basic mutator with a mutational stage
|
||||||
|
let mutator = HavocBytesMutator::default();
|
||||||
|
let stage = StdMutationalStage::new(mutator);
|
||||||
|
|
||||||
|
// A fuzzer with just one stage and a random policy to get testcasess from the corpus
|
||||||
|
let fuzzer = StdFuzzer::new(RandCorpusScheduler::new(), tuple_list!(stage));
|
||||||
|
|
||||||
|
// Create the executor for an in-process function with just one observer for edge coverage
|
||||||
|
let mut executor = InProcessExecutor::new(
|
||||||
|
"in-process(edges)",
|
||||||
|
harness,
|
||||||
|
tuple_list!(edges_observer),
|
||||||
|
&mut state,
|
||||||
|
&mut restarting_mgr,
|
||||||
|
);
|
||||||
|
|
||||||
|
// The actual target run starts here.
|
||||||
|
// Call LLVMFUzzerInitialize() if present.
|
||||||
|
unsafe {
|
||||||
|
if afl_libfuzzer_init() == -1 {
|
||||||
|
println!("Warning: LLVMFuzzerInitialize failed with -1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case the corpus is empty (on first run), reset
|
||||||
|
if state.corpus().count() < 1 {
|
||||||
|
state
|
||||||
|
.load_initial_inputs(
|
||||||
|
&mut executor,
|
||||||
|
&mut restarting_mgr,
|
||||||
|
fuzzer.scheduler(),
|
||||||
|
&corpus_dirs,
|
||||||
|
)
|
||||||
|
.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)?;
|
||||||
|
|
||||||
|
// Never reached
|
||||||
|
Ok(())
|
||||||
|
}
|
9
fuzzers/libfuzzer_libmozjpeg/start.sh
Executable file
9
fuzzers/libfuzzer_libmozjpeg/start.sh
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
#!/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
|
||||||
|
|
2
fuzzers/libfuzzer_libmozjpeg/stop.sh
Executable file
2
fuzzers/libfuzzer_libmozjpeg/stop.sh
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
killall -9 .libfuzzer_test.elf
|
17
fuzzers/libfuzzer_libmozjpeg/test.sh
Executable file
17
fuzzers/libfuzzer_libmozjpeg/test.sh
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
#!/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
|
@ -14,8 +14,7 @@ use libafl::{
|
|||||||
feedbacks::{CrashFeedback, MaxMapFeedback},
|
feedbacks::{CrashFeedback, MaxMapFeedback},
|
||||||
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer},
|
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer},
|
||||||
inputs::Input,
|
inputs::Input,
|
||||||
mutators::scheduled::HavocBytesMutator,
|
mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens},
|
||||||
mutators::token_mutations::TokensMetadata,
|
|
||||||
observers::{HitcountsMapObserver, StdMapObserver},
|
observers::{HitcountsMapObserver, StdMapObserver},
|
||||||
stages::mutational::StdMutationalStage,
|
stages::mutational::StdMutationalStage,
|
||||||
state::{HasCorpus, HasMetadata, State},
|
state::{HasCorpus, HasMetadata, State},
|
||||||
@ -54,7 +53,7 @@ where
|
|||||||
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
|
||||||
//RegistryBuilder::register::<TokensMetadata>();
|
//RegistryBuilder::register::<Tokens>();
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"Workdir: {:?}",
|
"Workdir: {:?}",
|
||||||
@ -109,8 +108,8 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
|
|||||||
println!("We're a client, let's fuzz :)");
|
println!("We're a client, let's fuzz :)");
|
||||||
|
|
||||||
// Create a PNG dictionary if not existing
|
// Create a PNG dictionary if not existing
|
||||||
if state.metadatas().get::<TokensMetadata>().is_none() {
|
if state.metadata().get::<Tokens>().is_none() {
|
||||||
state.add_metadata(TokensMetadata::new(vec![
|
state.add_metadata(Tokens::new(vec![
|
||||||
vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header
|
vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header
|
||||||
"IHDR".as_bytes().to_vec(),
|
"IHDR".as_bytes().to_vec(),
|
||||||
"IDAT".as_bytes().to_vec(),
|
"IDAT".as_bytes().to_vec(),
|
||||||
|
@ -136,7 +136,7 @@ where
|
|||||||
{
|
{
|
||||||
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.metadatas().get::<TopRatedsMetadata>().is_none() {
|
if state.metadata().get::<TopRatedsMetadata>().is_none() {
|
||||||
state.add_metadata(TopRatedsMetadata::new());
|
state.add_metadata(TopRatedsMetadata::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ where
|
|||||||
{
|
{
|
||||||
let mut entry = state.corpus().get(idx)?.borrow_mut();
|
let mut entry = state.corpus().get(idx)?.borrow_mut();
|
||||||
let factor = F::compute(&mut *entry)?;
|
let factor = F::compute(&mut *entry)?;
|
||||||
let meta = entry.metadatas().get::<M>().ok_or_else(|| {
|
let meta = entry.metadata().get::<M>().ok_or_else(|| {
|
||||||
Error::KeyNotFound(format!(
|
Error::KeyNotFound(format!(
|
||||||
"Metadata needed for MinimizerCorpusScheduler not found in testcase #{}",
|
"Metadata needed for MinimizerCorpusScheduler not found in testcase #{}",
|
||||||
idx
|
idx
|
||||||
@ -152,7 +152,7 @@ where
|
|||||||
})?;
|
})?;
|
||||||
for elem in meta.as_slice() {
|
for elem in meta.as_slice() {
|
||||||
if let Some(old_idx) = state
|
if let Some(old_idx) = state
|
||||||
.metadatas()
|
.metadata()
|
||||||
.get::<TopRatedsMetadata>()
|
.get::<TopRatedsMetadata>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.map
|
.map
|
||||||
@ -169,7 +169,7 @@ where
|
|||||||
|
|
||||||
for pair in new_favoreds {
|
for pair in new_favoreds {
|
||||||
state
|
state
|
||||||
.metadatas_mut()
|
.metadata_mut()
|
||||||
.get_mut::<TopRatedsMetadata>()
|
.get_mut::<TopRatedsMetadata>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.map
|
.map
|
||||||
@ -179,17 +179,17 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn cull(&self, state: &mut S) -> Result<(), Error> {
|
pub fn cull(&self, state: &mut S) -> Result<(), Error> {
|
||||||
if state.metadatas().get::<TopRatedsMetadata>().is_none() {
|
if state.metadata().get::<TopRatedsMetadata>().is_none() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let mut acc = HashSet::new();
|
let mut acc = HashSet::new();
|
||||||
let top_rated = state.metadatas().get::<TopRatedsMetadata>().unwrap();
|
let top_rated = state.metadata().get::<TopRatedsMetadata>().unwrap();
|
||||||
|
|
||||||
for key in top_rated.map.keys() {
|
for key in top_rated.map.keys() {
|
||||||
if !acc.contains(key) {
|
if !acc.contains(key) {
|
||||||
let idx = top_rated.map.get(key).unwrap();
|
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.metadatas().get::<M>().ok_or_else(|| {
|
let meta = entry.metadata().get::<M>().ok_or_else(|| {
|
||||||
Error::KeyNotFound(format!(
|
Error::KeyNotFound(format!(
|
||||||
"Metadata needed for MinimizerCorpusScheduler not found in testcase #{}",
|
"Metadata needed for MinimizerCorpusScheduler not found in testcase #{}",
|
||||||
idx
|
idx
|
||||||
|
@ -37,7 +37,7 @@ where
|
|||||||
fn add(&mut self, mut testcase: Testcase<I>) -> Result<usize, Error> {
|
fn add(&mut self, mut testcase: Testcase<I>) -> Result<usize, Error> {
|
||||||
match testcase.filename() {
|
match testcase.filename() {
|
||||||
None => {
|
None => {
|
||||||
// TODO walk entry metadatas to ask for pices of filename (e.g. :havoc in AFL)
|
// TODO walk entry metadata to ask for pices of filename (e.g. :havoc in AFL)
|
||||||
let filename = self.dir_path.join(format!("id_{}", &self.entries.len()));
|
let filename = self.dir_path.join(format!("id_{}", &self.entries.len()));
|
||||||
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());
|
||||||
|
@ -25,8 +25,8 @@ where
|
|||||||
filename: Option<String>,
|
filename: Option<String>,
|
||||||
/// Accumulated fitness from all the feedbacks
|
/// Accumulated fitness from all the feedbacks
|
||||||
fitness: u32,
|
fitness: u32,
|
||||||
/// Map of metadatas associated with this testcase
|
/// Map of metadata associated with this testcase
|
||||||
metadatas: SerdeAnyMap,
|
metadata: SerdeAnyMap,
|
||||||
/// Time needed to execute the input
|
/// Time needed to execute the input
|
||||||
exec_time: Option<Duration>,
|
exec_time: Option<Duration>,
|
||||||
/// Cached len of the input, if any
|
/// Cached len of the input, if any
|
||||||
@ -37,16 +37,16 @@ impl<I> HasMetadata for Testcase<I>
|
|||||||
where
|
where
|
||||||
I: Input,
|
I: Input,
|
||||||
{
|
{
|
||||||
/// Get all the metadatas into an HashMap
|
/// Get all the metadata into an HashMap
|
||||||
#[inline]
|
#[inline]
|
||||||
fn metadatas(&self) -> &SerdeAnyMap {
|
fn metadata(&self) -> &SerdeAnyMap {
|
||||||
&self.metadatas
|
&self.metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all the metadatas into an HashMap (mutable)
|
/// Get all the metadata into an HashMap (mutable)
|
||||||
#[inline]
|
#[inline]
|
||||||
fn metadatas_mut(&mut self) -> &mut SerdeAnyMap {
|
fn metadata_mut(&mut self) -> &mut SerdeAnyMap {
|
||||||
&mut self.metadatas
|
&mut self.metadata
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +158,7 @@ where
|
|||||||
input: Some(input.into()),
|
input: Some(input.into()),
|
||||||
filename: None,
|
filename: None,
|
||||||
fitness: 0,
|
fitness: 0,
|
||||||
metadatas: SerdeAnyMap::new(),
|
metadata: SerdeAnyMap::new(),
|
||||||
exec_time: None,
|
exec_time: None,
|
||||||
cached_len: None,
|
cached_len: None,
|
||||||
}
|
}
|
||||||
@ -171,7 +171,7 @@ where
|
|||||||
input: Some(input),
|
input: Some(input),
|
||||||
filename: Some(filename),
|
filename: Some(filename),
|
||||||
fitness: 0,
|
fitness: 0,
|
||||||
metadatas: SerdeAnyMap::new(),
|
metadata: SerdeAnyMap::new(),
|
||||||
exec_time: None,
|
exec_time: None,
|
||||||
cached_len: None,
|
cached_len: None,
|
||||||
}
|
}
|
||||||
@ -184,7 +184,7 @@ where
|
|||||||
input: Some(input.into()),
|
input: Some(input.into()),
|
||||||
filename: None,
|
filename: None,
|
||||||
fitness: fitness,
|
fitness: fitness,
|
||||||
metadatas: SerdeAnyMap::new(),
|
metadata: SerdeAnyMap::new(),
|
||||||
exec_time: None,
|
exec_time: None,
|
||||||
cached_len: None,
|
cached_len: None,
|
||||||
}
|
}
|
||||||
@ -196,7 +196,7 @@ where
|
|||||||
input: None,
|
input: None,
|
||||||
filename: None,
|
filename: None,
|
||||||
fitness: 0,
|
fitness: 0,
|
||||||
metadatas: SerdeAnyMap::new(),
|
metadata: SerdeAnyMap::new(),
|
||||||
exec_time: None,
|
exec_time: None,
|
||||||
cached_len: None,
|
cached_len: None,
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ where
|
|||||||
I: Input,
|
I: Input,
|
||||||
{
|
{
|
||||||
// TODO use an ID to keep track of the original index in the sender Corpus
|
// TODO use an ID to keep track of the original index in the sender Corpus
|
||||||
// The sender can then use it to send Testcase metadatas with CustomEvent
|
// The sender can then use it to send Testcase metadata with CustomEvent
|
||||||
/// A fuzzer found a new testcase. Rejoice!
|
/// A fuzzer found a new testcase. Rejoice!
|
||||||
NewTestcase {
|
NewTestcase {
|
||||||
/// The input for the new testcase
|
/// The input for the new testcase
|
||||||
|
@ -11,12 +11,6 @@ use crate::{
|
|||||||
use alloc::{borrow::ToOwned, vec::Vec};
|
use alloc::{borrow::ToOwned, vec::Vec};
|
||||||
use core::cmp::{max, min};
|
use core::cmp::{max, min};
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use std::{
|
|
||||||
fs::File,
|
|
||||||
io::{BufRead, BufReader},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The result of a mutation.
|
/// The result of a mutation.
|
||||||
/// If the mutation got skipped, the target
|
/// If the mutation got skipped, the target
|
||||||
/// will not be executed with the returned input.
|
/// will not be executed with the returned input.
|
||||||
@ -810,75 +804,8 @@ pub fn str_decode(item: &str) -> Result<Vec<u8>, Error> {
|
|||||||
return Ok(token);
|
return Ok(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a token to a dictionary, checking it is not a duplicate
|
|
||||||
pub fn add_token(tokens: &mut Vec<Vec<u8>>, token: &Vec<u8>) -> u32 {
|
|
||||||
if tokens.contains(token) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
tokens.push(token.to_vec());
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read a dictionary file and return the number of entries read
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
pub fn read_tokens_file(f: &str, tokens: &mut Vec<Vec<u8>>) -> Result<u32, Error> {
|
|
||||||
let mut entries = 0;
|
|
||||||
|
|
||||||
println!("Loading tokens file {:?} ...", &f);
|
|
||||||
|
|
||||||
let file = File::open(&f)?; // panic if not found
|
|
||||||
let reader = BufReader::new(file);
|
|
||||||
|
|
||||||
for line in reader.lines() {
|
|
||||||
let line = line.unwrap();
|
|
||||||
let line = line.trim_start().trim_end();
|
|
||||||
|
|
||||||
// we are only interested in '"..."', not prefixed 'foo = '
|
|
||||||
let start = line.chars().nth(0);
|
|
||||||
if line.len() == 0 || start == Some('#') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let pos_quote = match line.find("\"") {
|
|
||||||
Some(x) => x,
|
|
||||||
_ => return Err(Error::IllegalArgument("Illegal line: ".to_owned() + line)),
|
|
||||||
};
|
|
||||||
if line.chars().nth(line.len() - 1) != Some('"') {
|
|
||||||
return Err(Error::IllegalArgument("Illegal line: ".to_owned() + line));
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract item
|
|
||||||
let item = match line.get(pos_quote + 1..line.len() - 1) {
|
|
||||||
Some(x) => x,
|
|
||||||
_ => return Err(Error::IllegalArgument("Illegal line: ".to_owned() + line)),
|
|
||||||
};
|
|
||||||
if item.len() == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// decode
|
|
||||||
let token: Vec<u8> = match str_decode(item) {
|
|
||||||
Ok(val) => val,
|
|
||||||
Err(_) => {
|
|
||||||
return Err(Error::IllegalArgument(
|
|
||||||
"Illegal line (hex decoding): ".to_owned() + line,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// add
|
|
||||||
entries += add_token(tokens, &token);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(entries)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use crate::mutators::read_tokens_file;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -889,26 +816,6 @@ mod tests {
|
|||||||
utils::StdRand,
|
utils::StdRand,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
#[test]
|
|
||||||
fn test_read_tokens() {
|
|
||||||
let _ = fs::remove_file("test.tkns");
|
|
||||||
let data = r###"
|
|
||||||
# comment
|
|
||||||
token1@123="AAA"
|
|
||||||
token1="A\x41A"
|
|
||||||
"A\AA"
|
|
||||||
token2="B"
|
|
||||||
"###;
|
|
||||||
fs::write("test.tkns", data).expect("Unable to write test.tkns");
|
|
||||||
let mut v: Vec<Vec<u8>> = Vec::new();
|
|
||||||
let res = read_tokens_file(&"test.tkns".to_string(), &mut v).unwrap();
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
println!("Token file entries: {:?}", res);
|
|
||||||
assert_eq!(res, 2);
|
|
||||||
let _ = fs::remove_file("test.tkns");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mutators() {
|
fn test_mutators() {
|
||||||
let mut inputs = vec![
|
let mut inputs = vec![
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
//! Tokens are what afl calls extras or dictionaries.
|
//! Tokens are what afl calls extras or dictionaries.
|
||||||
//! They may be inserted as part of mutations during fuzzing.
|
//! They may be inserted as part of mutations during fuzzing.
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{BufRead, BufReader},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
inputs::{HasBytesVec, Input},
|
inputs::{HasBytesVec, Input},
|
||||||
mutators::*,
|
mutators::*,
|
||||||
@ -16,15 +23,101 @@ use mutations::buffer_copy;
|
|||||||
|
|
||||||
/// A state metadata holding a list of tokens
|
/// A state metadata holding a list of tokens
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct TokensMetadata {
|
pub struct Tokens {
|
||||||
tokens: Vec<Vec<u8>>,
|
token_vec: Vec<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::impl_serdeany!(TokensMetadata);
|
crate::impl_serdeany!(Tokens);
|
||||||
|
|
||||||
impl TokensMetadata {
|
/// The metadata used for token mutators
|
||||||
pub fn new(tokens: Vec<Vec<u8>>) -> Self {
|
impl Tokens {
|
||||||
Self { tokens: tokens }
|
/// Creates a new tokens metadata (old-skool afl name: `dictornary`)
|
||||||
|
pub fn new(token_vec: Vec<Vec<u8>>) -> Self {
|
||||||
|
Self { token_vec }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new instance from a file
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub fn from_tokens_file<P>(file: P) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let mut ret = Self::new(vec![]);
|
||||||
|
ret.add_tokens_from_file(file)?;
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a token to a dictionary, checking it is not a duplicate
|
||||||
|
/// Returns `false` if the token was already present and did not get added.
|
||||||
|
pub fn add_token(&mut self, token: &Vec<u8>) -> bool {
|
||||||
|
if self.token_vec.contains(token) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.token_vec.push(token.to_vec());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a tokens file, returning the count of new entries read
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub fn add_tokens_from_file<P>(&mut self, file: P) -> Result<u32, Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let mut entries = 0;
|
||||||
|
|
||||||
|
// println!("Loading tokens file {:?} ...", file);
|
||||||
|
|
||||||
|
let file = File::open(file)?; // panic if not found
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
|
||||||
|
for line in reader.lines() {
|
||||||
|
let line = line.unwrap();
|
||||||
|
let line = line.trim_start().trim_end();
|
||||||
|
|
||||||
|
// we are only interested in '"..."', not prefixed 'foo = '
|
||||||
|
let start = line.chars().nth(0);
|
||||||
|
if line.len() == 0 || start == Some('#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let pos_quote = match line.find("\"") {
|
||||||
|
Some(x) => x,
|
||||||
|
_ => return Err(Error::IllegalArgument("Illegal line: ".to_owned() + line)),
|
||||||
|
};
|
||||||
|
if line.chars().nth(line.len() - 1) != Some('"') {
|
||||||
|
return Err(Error::IllegalArgument("Illegal line: ".to_owned() + line));
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract item
|
||||||
|
let item = match line.get(pos_quote + 1..line.len() - 1) {
|
||||||
|
Some(x) => x,
|
||||||
|
_ => return Err(Error::IllegalArgument("Illegal line: ".to_owned() + line)),
|
||||||
|
};
|
||||||
|
if item.len() == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode
|
||||||
|
let token: Vec<u8> = match str_decode(item) {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(Error::IllegalArgument(
|
||||||
|
"Illegal line (hex decoding): ".to_owned() + line,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// add
|
||||||
|
if self.add_token(&token) {
|
||||||
|
entries += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the tokens stored in this db
|
||||||
|
pub fn tokens(&self) -> &[Vec<u8>] {
|
||||||
|
return &self.token_vec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,22 +130,22 @@ where
|
|||||||
{
|
{
|
||||||
let max_size = state.max_size();
|
let max_size = state.max_size();
|
||||||
let tokens_len = {
|
let tokens_len = {
|
||||||
let meta = state.metadatas().get::<TokensMetadata>();
|
let meta = state.metadata().get::<Tokens>();
|
||||||
if meta.is_none() {
|
if meta.is_none() {
|
||||||
return Ok(MutationResult::Skipped);
|
return Ok(MutationResult::Skipped);
|
||||||
}
|
}
|
||||||
if meta.unwrap().tokens.len() == 0 {
|
if meta.unwrap().tokens().len() == 0 {
|
||||||
return Ok(MutationResult::Skipped);
|
return Ok(MutationResult::Skipped);
|
||||||
}
|
}
|
||||||
meta.unwrap().tokens.len()
|
meta.unwrap().tokens().len()
|
||||||
};
|
};
|
||||||
let token_idx = state.rand_mut().below(tokens_len as u64) as usize;
|
let token_idx = state.rand_mut().below(tokens_len as u64) as usize;
|
||||||
|
|
||||||
let size = input.bytes().len();
|
let size = input.bytes().len();
|
||||||
let off = state.rand_mut().below((size + 1) as u64) as usize;
|
let off = state.rand_mut().below((size + 1) as u64) as usize;
|
||||||
|
|
||||||
let meta = state.metadatas().get::<TokensMetadata>().unwrap();
|
let meta = state.metadata().get::<Tokens>().unwrap();
|
||||||
let token = &meta.tokens[token_idx];
|
let token = &meta.tokens()[token_idx];
|
||||||
let mut len = token.len();
|
let mut len = token.len();
|
||||||
|
|
||||||
if size + len > max_size {
|
if size + len > max_size {
|
||||||
@ -83,21 +176,21 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let tokens_len = {
|
let tokens_len = {
|
||||||
let meta = state.metadatas().get::<TokensMetadata>();
|
let meta = state.metadata().get::<Tokens>();
|
||||||
if meta.is_none() {
|
if meta.is_none() {
|
||||||
return Ok(MutationResult::Skipped);
|
return Ok(MutationResult::Skipped);
|
||||||
}
|
}
|
||||||
if meta.unwrap().tokens.len() == 0 {
|
if meta.unwrap().tokens().len() == 0 {
|
||||||
return Ok(MutationResult::Skipped);
|
return Ok(MutationResult::Skipped);
|
||||||
}
|
}
|
||||||
meta.unwrap().tokens.len()
|
meta.unwrap().tokens().len()
|
||||||
};
|
};
|
||||||
let token_idx = state.rand_mut().below(tokens_len as u64) as usize;
|
let token_idx = state.rand_mut().below(tokens_len as u64) as usize;
|
||||||
|
|
||||||
let off = state.rand_mut().below(size as u64) as usize;
|
let off = state.rand_mut().below(size as u64) as usize;
|
||||||
|
|
||||||
let meta = state.metadatas().get::<TokensMetadata>().unwrap();
|
let meta = state.metadata().get::<Tokens>().unwrap();
|
||||||
let token = &meta.tokens[token_idx];
|
let token = &meta.tokens()[token_idx];
|
||||||
let mut len = token.len();
|
let mut len = token.len();
|
||||||
if off + len > size {
|
if off + len > size {
|
||||||
len = size - off;
|
len = size - off;
|
||||||
@ -107,3 +200,30 @@ where
|
|||||||
|
|
||||||
Ok(MutationResult::Mutated)
|
Ok(MutationResult::Mutated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
use super::Tokens;
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
#[test]
|
||||||
|
fn test_read_tokens() {
|
||||||
|
let _ = fs::remove_file("test.tkns");
|
||||||
|
let data = r###"
|
||||||
|
# comment
|
||||||
|
token1@123="AAA"
|
||||||
|
token1="A\x41A"
|
||||||
|
"A\AA"
|
||||||
|
token2="B"
|
||||||
|
"###;
|
||||||
|
fs::write("test.tkns", data).expect("Unable to write test.tkns");
|
||||||
|
let tokens = Tokens::from_tokens_file(&"test.tkns").unwrap();
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
println!("Token file entries: {:?}", tokens.tokens());
|
||||||
|
assert_eq!(tokens.tokens().len(), 2);
|
||||||
|
let _ = fs::remove_file("test.tkns");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -73,9 +73,9 @@ where
|
|||||||
/// Trait for elements offering metadata
|
/// Trait for elements offering metadata
|
||||||
pub trait HasMetadata {
|
pub trait HasMetadata {
|
||||||
/// A map, storing all metadata
|
/// A map, storing all metadata
|
||||||
fn metadatas(&self) -> &SerdeAnyMap;
|
fn metadata(&self) -> &SerdeAnyMap;
|
||||||
/// A map, storing all metadata (mut)
|
/// A map, storing all metadata (mut)
|
||||||
fn metadatas_mut(&mut self) -> &mut SerdeAnyMap;
|
fn metadata_mut(&mut self) -> &mut SerdeAnyMap;
|
||||||
|
|
||||||
/// Add a metadata to the metadata map
|
/// Add a metadata to the metadata map
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -83,7 +83,7 @@ pub trait HasMetadata {
|
|||||||
where
|
where
|
||||||
M: SerdeAny,
|
M: SerdeAny,
|
||||||
{
|
{
|
||||||
self.metadatas_mut().insert(meta);
|
self.metadata_mut().insert(meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check for a metadata
|
/// Check for a metadata
|
||||||
@ -92,7 +92,7 @@ pub trait HasMetadata {
|
|||||||
where
|
where
|
||||||
M: SerdeAny,
|
M: SerdeAny,
|
||||||
{
|
{
|
||||||
self.metadatas().get::<M>().is_some()
|
self.metadata().get::<M>().is_some()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,13 +317,13 @@ where
|
|||||||
{
|
{
|
||||||
/// Get all the metadata into an HashMap
|
/// Get all the metadata into an HashMap
|
||||||
#[inline]
|
#[inline]
|
||||||
fn metadatas(&self) -> &SerdeAnyMap {
|
fn metadata(&self) -> &SerdeAnyMap {
|
||||||
&self.metadata
|
&self.metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all the metadata into an HashMap (mutable)
|
/// Get all the metadata into an HashMap (mutable)
|
||||||
#[inline]
|
#[inline]
|
||||||
fn metadatas_mut(&mut self) -> &mut SerdeAnyMap {
|
fn metadata_mut(&mut self) -> &mut SerdeAnyMap {
|
||||||
&mut self.metadata
|
&mut self.metadata
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user