| // 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 |