mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-26 12:43:36 +01:00
[libfuzzer] arbitrary function adapter.
The adapter automates converting sequence of bytes into arbitrary arguments. Differential Revision: http://reviews.llvm.org/D17829 llvm-svn: 262673
This commit is contained in:
parent
347970f4ae
commit
f9401b724f
175
lib/Fuzzer/FuzzerFnAdapter.h
Normal file
175
lib/Fuzzer/FuzzerFnAdapter.h
Normal file
@ -0,0 +1,175 @@
|
||||
//===- FuzzerAdapter.h - Arbitrary function Fuzzer adapter -------*- C++ -*===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// W A R N I N G : E X P E R I M E N T A L.
|
||||
//
|
||||
// Defines an adapter to fuzz functions with (almost) arbitrary signatures.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_FUZZER_ADAPTER_H
|
||||
#define LLVM_FUZZER_ADAPTER_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
namespace fuzzer {
|
||||
|
||||
/// Unpacks bytes from \p Data according to \p F argument types
|
||||
/// and calls the function.
|
||||
/// Use to automatically adapt LLVMFuzzerTestOneInput interface to
|
||||
/// a specific function.
|
||||
/// Supported argument types: primitive types, std::vector<uint8_t>.
|
||||
template <typename Fn> bool Adapt(Fn F, const uint8_t *Data, size_t Size);
|
||||
|
||||
// The implementation performs several steps:
|
||||
// - function argument types are obtained (Args...)
|
||||
// - data is unpacked into std::tuple<Args...> one by one
|
||||
// - function is called with std::tuple<Args...> containing arguments.
|
||||
namespace impl {
|
||||
|
||||
// Single argument unpacking.
|
||||
|
||||
template <typename T>
|
||||
size_t UnpackPrimitive(const uint8_t *Data, size_t Size, T *Value) {
|
||||
if (Size < sizeof(T))
|
||||
return Size;
|
||||
*Value = *reinterpret_cast<const T *>(Data);
|
||||
return Size - sizeof(T);
|
||||
}
|
||||
|
||||
/// Unpacks into a given Value and returns the Size - num_consumed_bytes.
|
||||
/// Return value equal to Size signals inability to unpack the data (typically
|
||||
/// because there are not enough bytes).
|
||||
template <typename T>
|
||||
size_t UnpackSingle(const uint8_t *Data, size_t Size, T *Value);
|
||||
|
||||
#define UNPACK_SINGLE_PRIMITIVE(Type) \
|
||||
template <> \
|
||||
size_t UnpackSingle<Type>(const uint8_t *Data, size_t Size, Type *Value) { \
|
||||
return UnpackPrimitive(Data, Size, Value); \
|
||||
}
|
||||
|
||||
UNPACK_SINGLE_PRIMITIVE(char)
|
||||
UNPACK_SINGLE_PRIMITIVE(signed char)
|
||||
UNPACK_SINGLE_PRIMITIVE(unsigned char)
|
||||
|
||||
UNPACK_SINGLE_PRIMITIVE(short int)
|
||||
UNPACK_SINGLE_PRIMITIVE(unsigned short int)
|
||||
|
||||
UNPACK_SINGLE_PRIMITIVE(int)
|
||||
UNPACK_SINGLE_PRIMITIVE(unsigned int)
|
||||
|
||||
UNPACK_SINGLE_PRIMITIVE(long int)
|
||||
UNPACK_SINGLE_PRIMITIVE(unsigned long int)
|
||||
|
||||
UNPACK_SINGLE_PRIMITIVE(bool)
|
||||
UNPACK_SINGLE_PRIMITIVE(wchar_t)
|
||||
|
||||
UNPACK_SINGLE_PRIMITIVE(float)
|
||||
UNPACK_SINGLE_PRIMITIVE(double)
|
||||
UNPACK_SINGLE_PRIMITIVE(long double)
|
||||
|
||||
#undef UNPACK_SINGLE_PRIMITIVE
|
||||
|
||||
template <>
|
||||
size_t UnpackSingle<std::vector<uint8_t>>(const uint8_t *Data, size_t Size,
|
||||
std::vector<uint8_t> *Value) {
|
||||
if (Size < 1)
|
||||
return Size;
|
||||
size_t Len = std::min(static_cast<size_t>(*Data), Size - 1);
|
||||
std::vector<uint8_t> V(Data + 1, Data + 1 + Len);
|
||||
Value->swap(V);
|
||||
return Size - Len - 1;
|
||||
}
|
||||
|
||||
// Unpacking into arbitrary tuple.
|
||||
|
||||
// Recursion guard.
|
||||
template <int N, typename TupleT>
|
||||
typename std::enable_if<N == std::tuple_size<TupleT>::value, bool>::type
|
||||
UnpackImpl(const uint8_t *Data, size_t Size, TupleT *Tuple) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Unpack tuple elements starting from Nth.
|
||||
template <int N, typename TupleT>
|
||||
typename std::enable_if<N < std::tuple_size<TupleT>::value, bool>::type
|
||||
UnpackImpl(const uint8_t *Data, size_t Size, TupleT *Tuple) {
|
||||
size_t NewSize = UnpackSingle(Data, Size, &std::get<N>(*Tuple));
|
||||
if (NewSize == Size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return UnpackImpl<N + 1, TupleT>(Data + (Size - NewSize), NewSize, Tuple);
|
||||
}
|
||||
|
||||
// Unpacks into arbitrary tuple and returns true if successful.
|
||||
template <typename... Args>
|
||||
bool Unpack(const uint8_t *Data, size_t Size, std::tuple<Args...> *Tuple) {
|
||||
return UnpackImpl<0, std::tuple<Args...>>(Data, Size, Tuple);
|
||||
}
|
||||
|
||||
// Helper integer sequence templates.
|
||||
|
||||
template <int...> struct Seq {};
|
||||
|
||||
template <int N, int... S> struct GenSeq : GenSeq<N - 1, N - 1, S...> {};
|
||||
|
||||
// GenSeq<N>::type is Seq<0, 1, ..., N-1>
|
||||
template <int... S> struct GenSeq<0, S...> { typedef Seq<S...> type; };
|
||||
|
||||
// Function signature introspection.
|
||||
|
||||
template <typename T> struct FnTraits {};
|
||||
|
||||
template <typename ReturnType, typename... Args>
|
||||
struct FnTraits<ReturnType (*)(Args...)> {
|
||||
enum { Arity = sizeof...(Args) };
|
||||
typedef std::tuple<Args...> ArgsTupleT;
|
||||
};
|
||||
|
||||
// Calling a function with arguments in a tuple.
|
||||
|
||||
template <typename Fn, int... S>
|
||||
void ApplyImpl(Fn F, const typename FnTraits<Fn>::ArgsTupleT &Params,
|
||||
Seq<S...>) {
|
||||
F(std::get<S>(Params)...);
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
void Apply(Fn F, const typename FnTraits<Fn>::ArgsTupleT &Params) {
|
||||
// S is Seq<0, ..., Arity-1>
|
||||
auto S = typename GenSeq<FnTraits<Fn>::Arity>::type();
|
||||
ApplyImpl(F, Params, S);
|
||||
}
|
||||
|
||||
// Unpacking data into arguments tuple of correct type and calling the function.
|
||||
template <typename Fn>
|
||||
bool UnpackAndApply(Fn F, const uint8_t *Data, size_t Size) {
|
||||
typename FnTraits<Fn>::ArgsTupleT Tuple;
|
||||
if (!Unpack(Data, Size, &Tuple))
|
||||
return false;
|
||||
|
||||
Apply(F, Tuple);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace impl
|
||||
|
||||
template <typename Fn> bool Adapt(Fn F, const uint8_t *Data, size_t Size) {
|
||||
return impl::UnpackAndApply(F, Data, Size);
|
||||
}
|
||||
|
||||
} // namespace fuzzer
|
||||
|
||||
#endif
|
@ -27,6 +27,7 @@ set(Tests
|
||||
RepeatedMemcmp
|
||||
SimpleCmpTest
|
||||
SimpleDictionaryTest
|
||||
SimpleFnAdapterTest
|
||||
SimpleHashTest
|
||||
SimpleTest
|
||||
StrcmpTest
|
||||
@ -90,6 +91,7 @@ include_directories(${LLVM_MAIN_SRC_DIR}/utils/unittest/googletest/include)
|
||||
|
||||
add_executable(LLVMFuzzer-Unittest
|
||||
FuzzerUnittest.cpp
|
||||
FuzzerFnAdapterUnittest.cpp
|
||||
$<TARGET_OBJECTS:LLVMFuzzerNoMainObjects>
|
||||
)
|
||||
|
||||
|
98
lib/Fuzzer/test/FuzzerFnAdapterUnittest.cpp
Normal file
98
lib/Fuzzer/test/FuzzerFnAdapterUnittest.cpp
Normal file
@ -0,0 +1,98 @@
|
||||
#include "FuzzerFnAdapter.h"
|
||||
#include "gtest/gtest-spi.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace fuzzer {
|
||||
namespace impl {
|
||||
|
||||
template <typename... Args>
|
||||
bool Unpack(std::tuple<Args...> *Tuple, std::initializer_list<uint8_t> data) {
|
||||
std::vector<uint8_t> V(data);
|
||||
return Unpack(V.data(), V.size(), Tuple);
|
||||
}
|
||||
|
||||
TEST(Unpack, Bool) {
|
||||
std::tuple<bool> T;
|
||||
EXPECT_TRUE(Unpack(&T, {1}));
|
||||
EXPECT_TRUE(std::get<0>(T));
|
||||
|
||||
EXPECT_TRUE(Unpack(&T, {0}));
|
||||
EXPECT_FALSE(std::get<0>(T));
|
||||
|
||||
EXPECT_FALSE(Unpack(&T, {}));
|
||||
}
|
||||
|
||||
TEST(Unpack, BoolBool) {
|
||||
std::tuple<bool, bool> T;
|
||||
EXPECT_TRUE(Unpack(&T, {1, 0}));
|
||||
EXPECT_TRUE(std::get<0>(T));
|
||||
EXPECT_FALSE(std::get<1>(T));
|
||||
|
||||
EXPECT_TRUE(Unpack(&T, {0, 1}));
|
||||
EXPECT_FALSE(std::get<0>(T));
|
||||
EXPECT_TRUE(std::get<1>(T));
|
||||
|
||||
EXPECT_FALSE(Unpack(&T, {}));
|
||||
EXPECT_FALSE(Unpack(&T, {10}));
|
||||
}
|
||||
|
||||
TEST(Unpack, BoolInt) {
|
||||
std::tuple<bool, int> T;
|
||||
EXPECT_TRUE(Unpack(&T, {1, 16, 2, 0, 0}));
|
||||
EXPECT_TRUE(std::get<0>(T));
|
||||
EXPECT_EQ(528, std::get<1>(T));
|
||||
|
||||
EXPECT_FALSE(Unpack(&T, {1, 2}));
|
||||
}
|
||||
|
||||
TEST(Unpack, Vector) {
|
||||
std::tuple<std::vector<uint8_t>> T;
|
||||
const auto &V = std::get<0>(T);
|
||||
|
||||
EXPECT_FALSE(Unpack(&T, {}));
|
||||
|
||||
EXPECT_TRUE(Unpack(&T, {0}));
|
||||
EXPECT_EQ(0ul, V.size());
|
||||
|
||||
EXPECT_TRUE(Unpack(&T, {0, 1, 2, 3}));
|
||||
EXPECT_EQ(0ul, V.size());
|
||||
|
||||
EXPECT_TRUE(Unpack(&T, {2}));
|
||||
EXPECT_EQ(0ul, V.size());
|
||||
|
||||
EXPECT_TRUE(Unpack(&T, {2, 3}));
|
||||
EXPECT_EQ(1ul, V.size());
|
||||
EXPECT_EQ(3, V[0]);
|
||||
|
||||
EXPECT_TRUE(Unpack(&T, {2, 9, 8}));
|
||||
EXPECT_EQ(2ul, V.size());
|
||||
EXPECT_EQ(9, V[0]);
|
||||
EXPECT_EQ(8, V[1]);
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
bool UnpackAndApply(Fn F, std::initializer_list<uint8_t> Data) {
|
||||
std::vector<uint8_t> V(Data);
|
||||
return UnpackAndApply(F, V.data(), V.size());
|
||||
}
|
||||
|
||||
static void fnBool(bool b) { EXPECT_TRUE(b); }
|
||||
|
||||
TEST(Apply, Bool) {
|
||||
EXPECT_FALSE(UnpackAndApply(fnBool, {}));
|
||||
EXPECT_TRUE(UnpackAndApply(fnBool, {1}));
|
||||
EXPECT_NONFATAL_FAILURE(UnpackAndApply(fnBool, {0}),
|
||||
"Actual: false\nExpected: true");
|
||||
}
|
||||
|
||||
static void fnInt(int i) { EXPECT_EQ(42, i); }
|
||||
|
||||
TEST(Apply, Int) {
|
||||
EXPECT_FALSE(UnpackAndApply(fnInt, {}));
|
||||
EXPECT_TRUE(UnpackAndApply(fnInt, {42, 0, 0, 0}));
|
||||
EXPECT_NONFATAL_FAILURE(UnpackAndApply(fnInt, {10, 0, 0, 0}),
|
||||
"Actual: 10\nExpected: 42");
|
||||
}
|
||||
|
||||
} // namespace impl
|
||||
} // namespace fuzzer
|
21
lib/Fuzzer/test/SimpleFnAdapterTest.cpp
Normal file
21
lib/Fuzzer/test/SimpleFnAdapterTest.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
// Simple test for a fuzzer Fn adapter. The fuzzer has to find two non-empty
|
||||
// vectors with the same content.
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include "FuzzerFnAdapter.h"
|
||||
|
||||
static void TestFn(std::vector<uint8_t> V1, std::vector<uint8_t> V2) {
|
||||
if (V1.size() > 0 && V1 == V2) {
|
||||
std::cout << "BINGO; Found the target, exiting\n";
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
fuzzer::Adapt(TestFn, Data, Size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
3
lib/Fuzzer/test/fuzzer-fn-adapter.test
Normal file
3
lib/Fuzzer/test/fuzzer-fn-adapter.test
Normal file
@ -0,0 +1,3 @@
|
||||
RUN: LLVMFuzzer-SimpleFnAdapterTest 2>&1 | FileCheck %s
|
||||
|
||||
CHECK: BINGO
|
Loading…
Reference in New Issue
Block a user