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

Add support for defining custom mutators #79

Merged
merged 8 commits into from
May 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,14 @@ cc = "1.0"

[features]
arbitrary-derive = ["arbitrary/derive"]

[workspace]
members = [
"./example",
"./example_arbitrary",
"./example_mutator",
]

[dev-dependencies]
flate2 = "1.0.20"
rand = "0.8.3"
17 changes: 17 additions & 0 deletions ci/script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ cd $(dirname $0)/..

export CARGO_TARGET_DIR=$(pwd)/target

cargo test --doc

pushd ./example
cargo rustc \
--release \
Expand Down Expand Up @@ -39,3 +41,18 @@ RUST_LIBFUZZER_DEBUG_PATH=$(pwd)/debug_output \
cat $(pwd)/debug_output
grep -q Rgb $(pwd)/debug_output
popd

pushd ./example_mutator
cargo rustc \
--release \
-- \
-Cpasses='sancov' \
-Cllvm-args=-sanitizer-coverage-level=3 \
-Cllvm-args=-sanitizer-coverage-trace-compares \
-Cllvm-args=-sanitizer-coverage-inline-8bit-counters \
-Cllvm-args=-sanitizer-coverage-stack-depth \
-Cllvm-args=-sanitizer-coverage-trace-geps \
-Cllvm-args=-sanitizer-coverage-prune-blocks=0 \
-Zsanitizer=address
(! $CARGO_TARGET_DIR/release/example_mutator -runs=10000000)
popd
3 changes: 0 additions & 3 deletions example/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,5 @@ version = "0.1.0"
authors = ["Simonas Kazlauskas <git@kazlauskas.me>"]
edition = "2018"

[workspace]
members = ["."]

[dependencies]
libfuzzer-sys = { path = ".." }
3 changes: 0 additions & 3 deletions example_arbitrary/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,5 @@ version = "0.1.0"
authors = ["Simonas Kazlauskas <git@kazlauskas.me>"]
edition = "2018"

[workspace]
members = ["."]

[dependencies]
libfuzzer-sys = { path = "..", features = ["arbitrary-derive"] }
1 change: 1 addition & 0 deletions example_mutator/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
crash-*
11 changes: 11 additions & 0 deletions example_mutator/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "example_mutator"
version = "0.1.0"
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
flate2 = "1.0.20"
libfuzzer-sys = { path = ".." }
55 changes: 55 additions & 0 deletions example_mutator/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#![no_main]

use flate2::{read::GzDecoder, write::GzEncoder, Compression};
use libfuzzer_sys::{fuzz_mutator, fuzz_target};
use std::io::{Read, Write};

fuzz_target!(|data: &[u8]| {
// Decompress the input data and crash if it starts with "boom".
if let Some(data) = decompress(data) {
if data.starts_with(b"boom") {
panic!();
}
}
});

fuzz_mutator!(
|data: &mut [u8], size: usize, max_size: usize, _seed: u32| {
// Decompress the input data. If that fails, use a dummy value.
let mut decompressed = decompress(&data[..size]).unwrap_or_else(|| b"hi".to_vec());

// Mutate the decompressed data with `libFuzzer`'s default mutator. Make
// the `decompressed` vec's extra capacity available for insertion
// mutations via `resize`.
let len = decompressed.len();
let cap = decompressed.capacity();
decompressed.resize(cap, 0);
let new_decompressed_size = libfuzzer_sys::fuzzer_mutate(&mut decompressed, len, cap);

// Recompress the mutated data.
let compressed = compress(&decompressed[..new_decompressed_size]);

// Copy the recompressed mutated data into `data` and return the new size.
let new_size = std::cmp::min(max_size, compressed.len());
data[..new_size].copy_from_slice(&compressed[..new_size]);
new_size
}
);

fn decompress(data: &[u8]) -> Option<Vec<u8>> {
let mut decoder = GzDecoder::new(data);
let mut decompressed = Vec::new();
if decoder.read_to_end(&mut decompressed).is_ok() {
Some(decompressed)
} else {
None
}
}

fn compress(data: &[u8]) -> Vec<u8> {
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder
.write_all(data)
.expect("writing into a vec is infallible");
encoder.finish().expect("writing into a vec is infallible")
}
36 changes: 0 additions & 36 deletions libfuzzer/CREDITS.TXT

This file was deleted.

1 change: 0 additions & 1 deletion libfuzzer/FuzzerBuiltins.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ inline uint32_t Bswap(uint32_t x) { return __builtin_bswap32(x); }
inline uint64_t Bswap(uint64_t x) { return __builtin_bswap64(x); }

inline uint32_t Clzll(unsigned long long X) { return __builtin_clzll(X); }
inline uint32_t Clz(unsigned long long X) { return __builtin_clz(X); }
inline int Popcountll(unsigned long long X) { return __builtin_popcountll(X); }

} // namespace fuzzer
Expand Down
6 changes: 0 additions & 6 deletions libfuzzer/FuzzerBuiltinsMsvc.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,6 @@ inline uint32_t Clzll(uint64_t X) {
return 64;
}

inline uint32_t Clz(uint32_t X) {
unsigned long LeadZeroIdx = 0;
if (_BitScanReverse(&LeadZeroIdx, X)) return 31 - LeadZeroIdx;
return 32;
}

inline int Popcountll(unsigned long long X) {
#if !defined(_M_ARM) && !defined(_M_X64)
return __popcnt(X) + __popcnt(X >> 32);
Expand Down
38 changes: 22 additions & 16 deletions libfuzzer/FuzzerCorpus.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ struct InputInfo {
// Power schedule.
bool NeedsEnergyUpdate = false;
double Energy = 0.0;
size_t SumIncidence = 0;
double SumIncidence = 0.0;
Vector<std::pair<uint32_t, uint16_t>> FeatureFreqs;

// Delete feature Idx and its frequency from FeatureFreqs.
Expand Down Expand Up @@ -74,27 +74,28 @@ struct InputInfo {
void UpdateEnergy(size_t GlobalNumberOfFeatures, bool ScalePerExecTime,
std::chrono::microseconds AverageUnitExecutionTime) {
Energy = 0.0;
SumIncidence = 0;
SumIncidence = 0.0;

// Apply add-one smoothing to locally discovered features.
for (auto F : FeatureFreqs) {
size_t LocalIncidence = F.second + 1;
Energy -= LocalIncidence * logl(LocalIncidence);
double LocalIncidence = F.second + 1;
Energy -= LocalIncidence * log(LocalIncidence);
SumIncidence += LocalIncidence;
}

// Apply add-one smoothing to locally undiscovered features.
// PreciseEnergy -= 0; // since logl(1.0) == 0)
SumIncidence += (GlobalNumberOfFeatures - FeatureFreqs.size());
// PreciseEnergy -= 0; // since log(1.0) == 0)
SumIncidence +=
static_cast<double>(GlobalNumberOfFeatures - FeatureFreqs.size());

// Add a single locally abundant feature apply add-one smoothing.
size_t AbdIncidence = NumExecutedMutations + 1;
Energy -= AbdIncidence * logl(AbdIncidence);
double AbdIncidence = static_cast<double>(NumExecutedMutations + 1);
Energy -= AbdIncidence * log(AbdIncidence);
SumIncidence += AbdIncidence;

// Normalize.
if (SumIncidence != 0)
Energy = (Energy / SumIncidence) + logl(SumIncidence);
Energy = Energy / SumIncidence + log(SumIncidence);

if (ScalePerExecTime) {
// Scaling to favor inputs with lower execution time.
Expand Down Expand Up @@ -213,6 +214,8 @@ class InputCorpus {
assert(!U.empty());
if (FeatureDebug)
Printf("ADD_TO_CORPUS %zd NF %zd\n", Inputs.size(), NumFeatures);
// Inputs.size() is cast to uint32_t below.
assert(Inputs.size() < std::numeric_limits<uint32_t>::max());
Inputs.push_back(new InputInfo());
InputInfo &II = *Inputs.back();
II.U = U;
Expand All @@ -224,7 +227,7 @@ class InputCorpus {
II.HasFocusFunction = HasFocusFunction;
// Assign maximal energy to the new seed.
II.Energy = RareFeatures.empty() ? 1.0 : log(RareFeatures.size());
II.SumIncidence = RareFeatures.size();
II.SumIncidence = static_cast<double>(RareFeatures.size());
II.NeedsEnergyUpdate = false;
std::sort(II.UniqFeatureSet.begin(), II.UniqFeatureSet.end());
ComputeSHA1(U.data(), U.size(), II.Sha1);
Expand Down Expand Up @@ -399,7 +402,7 @@ class InputCorpus {
// Zero energy seeds will never be fuzzed and remain zero energy.
if (II->Energy > 0.0) {
II->SumIncidence += 1;
II->Energy += logl(II->SumIncidence) / II->SumIncidence;
II->Energy += log(II->SumIncidence) / II->SumIncidence;
}
}

Expand All @@ -426,7 +429,8 @@ class InputCorpus {
NumUpdatedFeatures++;
if (FeatureDebug)
Printf("ADD FEATURE %zd sz %d\n", Idx, NewSize);
SmallestElementPerFeature[Idx] = Inputs.size();
// Inputs.size() is guaranteed to be less than UINT32_MAX by AddToCorpus.
SmallestElementPerFeature[Idx] = static_cast<uint32_t>(Inputs.size());
InputSizesPerFeature[Idx] = NewSize;
return true;
}
Expand Down Expand Up @@ -464,7 +468,7 @@ class InputCorpus {

static const bool FeatureDebug = false;

size_t GetFeature(size_t Idx) const { return InputSizesPerFeature[Idx]; }
uint32_t GetFeature(size_t Idx) const { return InputSizesPerFeature[Idx]; }

void ValidateFeatureSet() {
if (FeatureDebug)
Expand Down Expand Up @@ -539,9 +543,11 @@ class InputCorpus {

if (VanillaSchedule) {
for (size_t i = 0; i < N; i++)
Weights[i] = Inputs[i]->NumFeatures
? (i + 1) * (Inputs[i]->HasFocusFunction ? 1000 : 1)
: 0.;
Weights[i] =
Inputs[i]->NumFeatures
? static_cast<double>((i + 1) *
(Inputs[i]->HasFocusFunction ? 1000 : 1))
: 0.;
}

if (FeatureDebug) {
Expand Down
4 changes: 3 additions & 1 deletion libfuzzer/FuzzerDataFlowTrace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ bool BlockCoverage::AppendCoverage(std::istream &IN) {
CoveredBlocks.push_back(BB);
}
if (CoveredBlocks.empty()) return false;
// Ensures no CoverageVector is longer than UINT32_MAX.
uint32_t NumBlocks = CoveredBlocks.back();
CoveredBlocks.pop_back();
for (auto BB : CoveredBlocks)
Expand Down Expand Up @@ -200,7 +201,8 @@ bool DataFlowTrace::Init(const std::string &DirPath, std::string *FocusFunction,
Printf("INFO: AUTOFOCUS: %zd %s\n", FocusFuncIdx,
FunctionNames[FocusFuncIdx].c_str());
for (size_t i = 0; i < NumFunctions; i++) {
if (!Weights[i]) continue;
if (Weights[i] == 0.0)
continue;
Printf(" [%zd] W %g\tBB-tot %u\tBB-cov %u\tEntryFreq %u:\t%s\n", i,
Weights[i], Coverage.GetNumberOfBlocks(i),
Coverage.GetNumberOfCoveredBlocks(i), Coverage.GetCounter(i, 0),
Expand Down
14 changes: 8 additions & 6 deletions libfuzzer/FuzzerDataFlowTrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,17 @@ int CollectDataFlow(const std::string &DFTBinary, const std::string &DirPath,
const Vector<SizedFile> &CorporaFiles);

class BlockCoverage {
public:
public:
// These functions guarantee no CoverageVector is longer than UINT32_MAX.
bool AppendCoverage(std::istream &IN);
bool AppendCoverage(const std::string &S);

size_t NumCoveredFunctions() const { return Functions.size(); }

uint32_t GetCounter(size_t FunctionId, size_t BasicBlockId) {
auto It = Functions.find(FunctionId);
if (It == Functions.end()) return 0;
if (It == Functions.end())
return 0;
const auto &Counters = It->second;
if (BasicBlockId < Counters.size())
return Counters[BasicBlockId];
Expand All @@ -61,7 +63,7 @@ class BlockCoverage {
auto It = Functions.find(FunctionId);
if (It == Functions.end()) return 0;
const auto &Counters = It->second;
return Counters.size();
return static_cast<uint32_t>(Counters.size());
}

uint32_t GetNumberOfCoveredBlocks(size_t FunctionId) {
Expand All @@ -78,8 +80,7 @@ class BlockCoverage {
Vector<double> FunctionWeights(size_t NumFunctions) const;
void clear() { Functions.clear(); }

private:

private:
typedef Vector<uint32_t> CoverageVector;

uint32_t NumberOfCoveredBlocks(const CoverageVector &Counters) const {
Expand All @@ -91,7 +92,8 @@ class BlockCoverage {
}

uint32_t NumberOfUncoveredBlocks(const CoverageVector &Counters) const {
return Counters.size() - NumberOfCoveredBlocks(Counters);
return static_cast<uint32_t>(Counters.size()) -
NumberOfCoveredBlocks(Counters);
}

uint32_t SmallestNonZeroCounter(const CoverageVector &Counters) const {
Expand Down
8 changes: 5 additions & 3 deletions libfuzzer/FuzzerDictionary.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ template <size_t kMaxSizeT> class FixedWord {
public:
static const size_t kMaxSize = kMaxSizeT;
FixedWord() {}
FixedWord(const uint8_t *B, uint8_t S) { Set(B, S); }
FixedWord(const uint8_t *B, size_t S) { Set(B, S); }

void Set(const uint8_t *B, uint8_t S) {
void Set(const uint8_t *B, size_t S) {
static_assert(kMaxSizeT <= std::numeric_limits<uint8_t>::max(),
"FixedWord::kMaxSizeT cannot fit in a uint8_t.");
assert(S <= kMaxSize);
memcpy(Data, B, S);
Size = S;
Size = static_cast<uint8_t>(S);
}

bool operator==(const FixedWord<kMaxSize> &w) const {
Expand Down
Loading