blob: b6d97382a478e23838feb1ce653f85d900bd289b [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Small fuzz test examples for micro-benchmarking (and functional testing).
//
// The fuzz tests in this file are used by two higher level tests:
//
// (1) The `benchmark_test` uses these fuzz tests to benchmark the fuzzer, i.e.,
// to measure the number of iterations/time necessary to reach the "abort()"
// branch or some bug.
//
// (2) The `functional_test` uses them for basic functional end-to-end testing,
// i.e., to check that the fuzzer behaves as expected and outputs the expected
// results. E.g., the fuzzer finds the abort() or bug.
#include <cmath>
#include <cstdlib>
#include <string>
#include <utility>
#include "./fuzztest/fuzztest.h"
#include "./fuzztest/internal/test_protobuf.pb.h"
volatile int googlefuzz_force_write;
namespace {
// A microbenchmark to use as a baseline reference point.
void Iters10000(std::true_type) {
static int i = 0;
if (++i == 10000) std::abort();
}
FUZZ_TEST(Control, Iters10000);
// We use this test to make sure we catch buffer overflows.
void BufferOverread(std::string_view s) {
if (s.empty()) return;
size_t out_of_bounds_index = s.size();
googlefuzz_force_write = s[out_of_bounds_index];
}
void BufferOverreadWithStringView(std::string_view s) { BufferOverread(s); }
FUZZ_TEST(MySuite, BufferOverreadWithStringView)
.WithDomains(fuzztest::Arbitrary<std::string_view>());
void BufferOverreadWithString(std::string_view s) { BufferOverread(s); }
FUZZ_TEST(MySuite, BufferOverreadWithString)
.WithDomains(fuzztest::Arbitrary<std::string>());
void BufferOverreadWithStringAndLvalueStringViewRef(const std::string_view& s) {
BufferOverread(s);
}
FUZZ_TEST(MySuite, BufferOverreadWithStringAndLvalueStringViewRef)
.WithDomains(fuzztest::Arbitrary<std::string>());
void BufferOverreadWithStringAndRvalueStringViewRef(std::string_view&& s) {
BufferOverread(std::move(s));
}
FUZZ_TEST(MySuite, BufferOverreadWithStringAndRvalueStringViewRef)
.WithDomains(fuzztest::Arbitrary<std::string>());
// Always disable optimization for this example, otherwise (when optimization is
// enabled) SanCov doesn't instrument all edges (and therefore no feedback).
// TODO(b/182297432): Make this unnecessary.
__attribute__((optnone)) void Coverage(char a, char b, char c, char d) {
if (a == 'F') {
if (b == 'u') {
if (c == 'z') {
if (d == 'z') {
// Use an assert to test that these are caught in fuzzing mode, even
// when built with optimization.
assert(a == b);
}
}
}
}
}
FUZZ_TEST(MySuite, Coverage);
void DivByZero(int8_t numerator, int8_t denominator) {
googlefuzz_force_write = denominator != -1 && numerator / denominator != 0;
}
FUZZ_TEST(MySuite, DivByZero);
__attribute__((optnone)) void FindString(const std::string& in) {
// Do one character at a time to have one edge per char.
// No need to check for length, because for std::string s, s.[s.size()]
// returns null character.
if (in[0] == 'F' && in[1] == 'u' && in[2] == 'z' && in[3] == 'z') {
std::abort();
}
}
void String(const std::string& in) { FindString(in); }
FUZZ_TEST(MySuite, String);
// TODO(changochen): Replace it with better test function
void StringAsciiOnly(const std::string& in) { FindString(in); }
FUZZ_TEST(MySuite, StringAsciiOnly).WithDomains(fuzztest::AsciiString());
// TODO(changochen): Replace it with better test function
__attribute__((optnone)) void StringRegexp(const std::string& in) {
FindString(in);
}
FUZZ_TEST(MySuite, StringRegexp).WithDomains(fuzztest::InRegexp("[A-Za-z]{4}"));
__attribute__((optnone)) void StringView(std::string_view in) {
// Do one character at a time to have one edge per char.
if (in.size() >= 4 && in[0] == 'F' && in[1] == 'u' && in[2] == 'z' &&
in[3] == 'z') {
std::abort();
}
}
FUZZ_TEST(MySuite, StringView);
__attribute__((optnone)) void StrCmp(const std::string& s) {
if (strcmp(s.c_str(), "Hello!") == 0) {
std::abort();
}
}
FUZZ_TEST(MySuite, StrCmp);
__attribute__((optnone)) void BitFlags(int bits) {
if ((bits & (1 << 0)) != 0 && (bits & (1 << 1)) == 0 &&
(bits & (1 << 2)) != 0 && (bits & (1 << 3)) == 0 &&
(bits & (1 << 4)) != 0 && (bits & (1 << 5)) == 0) {
std::abort();
}
}
FUZZ_TEST(MySuite, BitFlags)
.WithDomains(fuzztest::BitFlagCombinationOf({1 << 0, 1 << 1, 1 << 2, 1 << 3,
1 << 4, 1 << 5}));
enum Color { red, green, blue };
auto AnyColor() { return fuzztest::ElementOf({red, green, blue}); }
__attribute__((optnone)) void EnumValue(Color a, Color b, Color c) {
if (a == red) {
if (b == green) {
if (c == blue) {
std::abort();
}
}
}
}
FUZZ_TEST(MySuite, EnumValue).WithDomains(AnyColor(), AnyColor(), AnyColor());
enum class ColorClass { red, green, blue };
auto AnyColorClass() {
return fuzztest::ElementOf(
{ColorClass::red, ColorClass::green, ColorClass::blue});
}
__attribute__((optnone)) void EnumClassValue(ColorClass a, ColorClass b,
ColorClass c) {
if (a == ColorClass::red) {
if (b == ColorClass::green) {
if (c == ColorClass::blue) {
std::abort();
}
}
}
}
FUZZ_TEST(MySuite, EnumClassValue)
.WithDomains(AnyColorClass(), AnyColorClass(), AnyColorClass());
__attribute__((optnone)) void Proto(
const fuzztest::internal::TestProtobuf& proto) {
if (proto.b() && //
proto.subproto().subproto_i32() > 1 && //
!proto.rep_i64().empty() && proto.rep_i64()[0] == -1 && //
!proto.rep_subproto().empty() &&
proto.rep_subproto(0).subproto_i32() > 0x12345 &&
proto.e() == proto.Label2) {
std::abort();
}
}
FUZZ_TEST(MySuite, Proto);
__attribute__((optnone)) void BitvectorValue(const std::vector<bool>& v) {
if (v == std::vector<bool>{true, false, true, false}) {
std::abort();
}
}
FUZZ_TEST(MySuite, BitvectorValue);
__attribute__((optnone)) void VectorValue(const std::vector<char>& v) {
if (v == std::vector<char>{'F', 'u', 'z', 'z'}) {
std::abort();
}
}
FUZZ_TEST(MySuite, VectorValue);
__attribute__((optnone)) void WithDomainClass(uint8_t a, double d) {
// This will only crash with a=10, to make it easier to check the results.
// d can have any value.
if (a > 10) return;
int x = 1000;
while (a > 0) {
--a;
x /= 2;
d = a / x;
static_cast<void>(d); // Silence -Wunused-but-set-parameter
}
}
FUZZ_TEST(MySuite, WithDomainClass)
.WithDomains(fuzztest::Domain<uint8_t>(fuzztest::Arbitrary<uint8_t>()),
fuzztest::Domain<double>(fuzztest::Arbitrary<double>()));
std::string NumberOfAnimalsToString(int num, std::string_view name) {
return absl::StrFormat("%d %ss", num, name);
}
int TimesTwo(int x) { return 2 * x; }
fuzztest::Domain<std::string> EvenNumberOfAnimals() {
return fuzztest::Map(
NumberOfAnimalsToString, fuzztest::Map(TimesTwo, fuzztest::InRange(1, 6)),
fuzztest::ElementOf<std::string_view>({"dog", "cat", "monkey"}));
}
void Mapping(const std::string& s) {
if (s == "12 monkeys") std::abort();
}
FUZZ_TEST(MySuite, Mapping).WithDomains(EvenNumberOfAnimals());
auto StringAndValidIndex(const std::string& s) {
return fuzztest::PairOf(fuzztest::Just(s),
fuzztest::InRange<size_t>(0, s.size() - 1));
}
auto AnyStringAndValidIndex() {
auto string_domain =
fuzztest::StringOf(fuzztest::InRange('a', 'z')).WithSize(3);
return fuzztest::FlatMap(StringAndValidIndex, string_domain);
}
void FlatMapping(const std::pair<std::string, size_t> str_and_idx) {
absl::string_view str = str_and_idx.first;
size_t idx = str_and_idx.second;
if (str == "abc" && idx == 2) {
std::abort();
}
}
FUZZ_TEST(MySuite, FlatMapping).WithDomains(AnyStringAndValidIndex());
void Filtering(int multiple_of_2, int square) {
// Should only fail with (8, 9)
if (multiple_of_2 + 1 == square) std::abort();
}
auto MultiplesOfTwo() {
auto is_even = [](int i) { return i % 2 == 0; };
return fuzztest::Filter(is_even, fuzztest::InRange(1, 10));
}
auto Squares() {
auto is_square = [](int i) {
return std::pow(std::floor(std::sqrt(i)), 2) == i;
};
return fuzztest::Filter(is_square, fuzztest::InRange(1, 10));
}
FUZZ_TEST(MySuite, Filtering).WithDomains(MultiplesOfTwo(), Squares());
void SmartPointer(std::unique_ptr<int> i) {
if (i && absl::StrCat(*i)[0] == '1') std::abort();
}
FUZZ_TEST(MySuite, SmartPointer);
void Minimizer(const std::string& input) {
if (input.find('X') != input.npos) std::abort();
}
FUZZ_TEST(MySuite, Minimizer);
struct MyStruct {
uint8_t a;
std::string b;
};
void MyStructArbitrary(MyStruct s) {
if (s.a == 0 && s.b[0] == 'X') std::abort();
}
FUZZ_TEST(MySuite, MyStructArbitrary);
void MyStructWithDomains(MyStruct s) {
if (s.a == 0 && s.b[0] == 'X') std::abort();
}
FUZZ_TEST(MySuite, MyStructWithDomains)
.WithDomains(fuzztest::StructOf<MyStruct>(
fuzztest::Arbitrary<uint8_t>(), fuzztest::Arbitrary<std::string>()));
auto RepeatedStringDomain() {
return fuzztest::ConstructorOf<std::string>(fuzztest::InRange(1, 5),
fuzztest::InRange('a', 'c'));
}
void ConstructorWithDomains(const std::string& s) {
if (s == "ccc") abort();
}
FUZZ_TEST(MySuite, ConstructorWithDomains).WithDomains(RepeatedStringDomain());
auto SeedInputIsUsedForMutation(const std::vector<uint32_t>& s) {
// Make it very hard for coverage to find the value.
// Will only abort() if seed input is mutated, i.e., if element `0xbad` is
// removed.
if (s.size() == 4) {
if (s[0] == 0 || s[1] == 0 || s[2] == 0 || s[3] == 0) {
return;
} else if (s[0] * 19 == 1979 * s[1] && 1234 * s[3] == s[2] * 5678) {
std::abort();
}
}
}
FUZZ_TEST(MySuite, SeedInputIsUsedForMutation)
.WithSeeds({{{1979, 19, 1234, 0xbad, 5678}}});
// Testing cmp coverage.
// Matching magic values for cmp instructions.
auto Int32ValueTest(int x) {
if (x == 0xdeadbeef) {
std::abort();
}
}
FUZZ_TEST(MySuite, Int32ValueTest);
// Testing cmp coverage.
// Matching magic values for switch instructions.
auto SwitchTest(unsigned int x) {
switch (x) {
case 0xbaadf00d:
abort();
case 0xdeadbeef:
abort();
default:
break;
}
}
FUZZ_TEST(MySuite, SwitchTest);
// Enabled by including absolute distance in the CMP coverage score.
// Path-dependends-on-states could be somehow explored by a fair
// scoring of the state data. With Absolute distance, this test will be
// captured.
auto PathDependentState(char a, char b, char c, char d) {
int counter = 0;
if (a == 'f') counter++;
if (b == 'u') counter++;
if (c == 'z') counter++;
if (d == 'z') counter++;
if (counter == 4) {
std::abort();
}
}
FUZZ_TEST(MySuite, PathDependentState);
// Testing new mutation of InRange domain
// works correctly.
auto Int32ValueInRangeTest(unsigned int x) {
if (x == 0x00110000) {
std::abort();
}
}
FUZZ_TEST(MySuite, Int32ValueInRangeTest)
.WithDomains(fuzztest::InRange(0x00100000U, 0x00111111U));
auto BasicStringCmpTest(absl::string_view encoded_data) {
if (encoded_data.size() < 8) {
return;
} else if (encoded_data.substr(0, 8) == "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a") {
std::abort();
}
}
FUZZ_TEST(MySuite, BasicStringCmpTest);
auto SimpleFormatParsingTest(absl::string_view encoded_data) {
if (encoded_data.size() < 72) {
return;
} else if (encoded_data.substr(0, 5) == "GUARD") {
if (encoded_data.substr(67, 72) == "DRAUG") {
// Put the magic number check in a random place and protected by guards
// so it will be very hard to find with random mutations.
const size_t magic_number_field =
*reinterpret_cast<const uint32_t*>(encoded_data.data() + 27);
if (magic_number_field == 0xdeadbeef) {
std::abort();
}
}
}
}
FUZZ_TEST(MySuite, SimpleFormatParsingTest);
// To test the manual dictionary. The dynamic (automatic) dictionary feature
// would not be effective cracking this, as we are comparing to the reversed
// string.
void StringReverseTest(const std::string encoded_data) {
if (encoded_data.size() >= 10) {
std::string reverse(encoded_data.rbegin(), encoded_data.rend());
if (reverse == "0123456789") {
std::abort();
}
}
}
FUZZ_TEST(MySuite, StringReverseTest)
.WithDomains(
fuzztest::Arbitrary<std::string>().WithDictionary({"9876543210"}));
////////////////////////////////////////////////////////////////////////////////
// Examples to get working.
#if 0
// Here we collect examples that are not working yet, but we'd like them to
// work. Once an example is working, we move it out above.
// Matching values.
FUZZ_TEST(MySuite, Int64Value, (uint64_t x)) {
if (x == 0xdeadbeefcafeface) {
std::abort();
}
}
FUZZ_TEST(MySuite, DoubleValue, (double x)) {
if (x == 3.14) {
std::abort();
}
}
FUZZ_TEST(MySuite, StringValue, (const std::string& s)) {
if (s == "Hello world!") {
std::abort();
}
}
FUZZ_TEST(MySuite, StringSize, (const std::string& s)) {
if (s.size >= 100) {
std::abort();
}
}
FUZZ_TEST(MySuite, StringPartialValue, (const std::string& s)) {
if (s.size >= 100 && s[16] == 'f' && s[32] == 'u' && s[48] =
'z' && s[64] == 'z') {
std::abort();
}
}
struct Order {
std::string item;
int quantity;
}
FUZZ_TEST(MySuite, StructValue, (const Order& o)) {
if (o.item == "bla" && o.quantity == 3) {
std::abort();
}
}
FUZZ_TEST(MySuite, ProtoValue, (const MyProtoMessage& msg)) {
MyProtoMessage value = ...;
if (google::protobuf::util::MessageDifferencer::Equals(msg, value)) {
std::abort();
}
}
// Constraints.
FUZZ_TEST(MySuite, Divisible, (uint64_6 x)) {
if ((x != 0) && (x % 2 == 0) && (x % 3 == 0) && (x % 5 == 0) &&
(x % 7 == 0) && (x % 11 == 0) && (x % 13 == 0)) {
std::abort();
}
}
FUZZ_TEST(MySuite, Divisible, (int x, int y)) {
// Possible solution: x = 8, y = -9.
if (x > y) {
if (x + y > x * y) {
if (x < -y) {
if (x * y != 0) {
if (x * -2 < y) {
if (x - 17 == y) {
std::abort();
}
}
}
}
}
}
}
FUZZ_TEST(MySuite, IncreasingNumbers, (int a, int b, int c, int d)) {
if (b == a + 1) {
if (c == b + 1) {
if (d == c + 1) {
std::abort();
}
}
}
}
FUZZ_TEST(MySuite, CharConstraints, (char a, char b, char c, char d)) {
if (isalnum(a)) {
if (isblank(b)) {
if (isupper(c)) {
if (ispunct(d)) {
std::abort();
}
}
}
}
}
// Program structures.
FUZZ_TEST(MySuite, Loop, (const std::string& s)) {
int counter = 0;
for (int i = 0; i < s.size(), i++) {
if (s[i] == i) {
counter++;
}
if (counter >= 10) {
std::abort();
}
}
}
void Recurse(const std::string& s, int i, int counter) {
if (counter >= 10) {
std::abort();
}
if (i < s.size()) {
Recurse(s, i + 1, counter + (s[i] == i));
}
}
FUZZ_TEST(MySuite, Recursion, (const std::string& s)) { Recurse(s, 0, 0); }
#endif // End of "examples to get working".
} // namespace