| #include <cstddef> |
| #include <cstdint> |
| #include <cstdlib> |
| #include <iostream> |
| #include <string> |
| #include <vector> |
| |
| #include "absl/flags/declare.h" |
| #include "absl/flags/flag.h" |
| #include "absl/log/check.h" |
| #include "absl/random/bit_gen_ref.h" |
| #include "absl/random/random.h" |
| #include "absl/strings/escaping.h" |
| #include "absl/strings/str_split.h" |
| #include "absl/strings/string_view.h" |
| #include "./fuzztest/fuzztest.h" |
| #include "./fuzztest/internal/domains/arbitrary_impl.h" |
| #include "./fuzztest/internal/domains/container_of_impl.h" |
| #include "./fuzztest/internal/domains/domain_base.h" |
| #include "./fuzztest/internal/io.h" |
| #include "re2/re2.h" |
| |
| ABSL_DECLARE_FLAG(std::string, llvm_fuzzer_wrapper_dict_file); |
| ABSL_DECLARE_FLAG(std::string, llvm_fuzzer_wrapper_corpus_dir); |
| |
| constexpr static size_t kByteArrayMaxLen = 4096; |
| |
| extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); |
| |
| extern "C" size_t __attribute__((weak)) |
| LLVMFuzzerCustomMutator(uint8_t* data, size_t size, size_t max_size, |
| unsigned int seed); |
| |
| // TODO(b/303267857): Migrate fuzz targets that use this feature manually. |
| // `LLVMFuzzerCustomCrossOver` is defined to produce a link-error (duplicate |
| // definition) if it's also defined by user. |
| extern "C" size_t LLVMFuzzerCustomCrossOver(const uint8_t* data1, size_t size1, |
| const uint8_t* data2, size_t size2, |
| uint8_t* out, size_t max_out_size, |
| unsigned int seed) { |
| std::cerr << "LLVMFuzzerCustomCrossOver is not supported in FuzzTest\n"; |
| exit(-1); |
| } |
| |
| std::vector<std::vector<uint8_t>> ReadByteArraysFromDirectory() { |
| const std::string flag = absl::GetFlag(FLAGS_llvm_fuzzer_wrapper_corpus_dir); |
| if (flag.empty()) return {}; |
| std::vector<fuzztest::internal::FilePathAndData> files = |
| fuzztest::internal::ReadFileOrDirectory(flag); |
| |
| std::vector<std::vector<uint8_t>> out; |
| out.reserve(files.size()); |
| for (const fuzztest::internal::FilePathAndData& file : files) { |
| out.push_back( |
| {file.data.begin(), |
| file.data.begin() + std::min(file.data.size(), kByteArrayMaxLen)}); |
| } |
| return out; |
| } |
| |
| std::vector<std::vector<uint8_t>> ReadByteArrayDictionaryFromFile() { |
| const std::string flag = absl::GetFlag(FLAGS_llvm_fuzzer_wrapper_dict_file); |
| if (flag.empty()) return {}; |
| std::vector<fuzztest::internal::FilePathAndData> files = |
| fuzztest::internal::ReadFileOrDirectory(flag); |
| |
| std::vector<std::vector<uint8_t>> out; |
| out.reserve(files.size()); |
| // Dictionary must be in the format specified at |
| // https://llvm.org/docs/LibFuzzer.html#dictionaries |
| constexpr absl::string_view kLineRegex = |
| "[^\\\"]*" // Skip an arbitrary prefix. |
| "\\\"(.+)\\\"" // Must be enclosed in quotes. |
| "[^\\\"]*"; // Skip an arbitrary suffix. |
| for (const fuzztest::internal::FilePathAndData& file : files) { |
| for (absl::string_view line : absl::StrSplit(file.data, '\n')) { |
| if (line.empty() || line[0] == '#') continue; |
| std::string entry; |
| CHECK(RE2::FullMatch(line, kLineRegex, &entry)) |
| << "Invalid dictionary entry: " << line; |
| std::string unescaped_entry; |
| CHECK(absl::CUnescape(entry, &unescaped_entry)) |
| << "Could not unescape: " << entry; |
| out.emplace_back(unescaped_entry.begin(), unescaped_entry.end()); |
| } |
| } |
| return out; |
| } |
| |
| template <typename T> |
| class InplaceVector { |
| public: |
| using iterator = T*; |
| using value_type = T; |
| |
| InplaceVector() : data_(nullptr), size_(0) {} |
| InplaceVector(T* data, std::size_t size) : data_(data), size_(size) {} |
| |
| T& operator[](int i) { return data_[i]; } |
| |
| T const& operator[](int i) const { return data_[i]; } |
| |
| std::size_t size() const { return size_; } |
| |
| T* begin() const { return data_; } |
| |
| T* end() const { return data_ + size_; } |
| |
| void insert(T* index, T val) { |
| for (T* itr = data_ + size_; itr > index; --itr) { |
| *itr = *(itr - 1); |
| } |
| *index = val; |
| ++size_; |
| } |
| |
| void erase(T* index) { |
| for (T* itr = index; itr < data_ + size_ - 1; ++itr) { |
| *itr = *(itr + 1); |
| } |
| --size_; |
| } |
| |
| void erase(T* begin, T* end) { |
| for (T *itr = begin, *jtr = end; jtr < data_ + size_; ++itr, ++jtr) { |
| *itr = *jtr; |
| } |
| size_ -= (end - begin); |
| } |
| |
| private: |
| T* data_; |
| std::size_t size_; |
| }; |
| |
| extern "C" size_t LLVMFuzzerMutate(uint8_t* data, size_t size, |
| size_t max_size) { |
| static auto domain = fuzztest::internal::SequenceContainerOfImpl< |
| InplaceVector<uint8_t>, fuzztest::internal::ArbitraryImpl<uint8_t>>(); |
| domain.WithMaxSize(max_size); |
| absl::BitGen bitgen; |
| InplaceVector<uint8_t> val(data, size); |
| domain.Mutate(val, bitgen, false); |
| return val.size(); |
| } |
| |
| class ArbitraryByteVector |
| : public fuzztest::internal::SequenceContainerOfImpl< |
| std::vector<uint8_t>, fuzztest::internal::ArbitraryImpl<uint8_t>> { |
| using Base = typename ArbitraryByteVector::ContainerOfImplBase; |
| |
| public: |
| using typename Base::corpus_type; |
| |
| ArbitraryByteVector() { WithMaxSize(kByteArrayMaxLen); } |
| |
| void Mutate(corpus_type& val, absl::BitGenRef prng, bool only_shrink) { |
| if (LLVMFuzzerCustomMutator) { |
| const size_t size = val.size(); |
| const size_t max_size = only_shrink ? size : kByteArrayMaxLen; |
| val.resize(max_size); |
| val.resize(LLVMFuzzerCustomMutator(val.data(), size, max_size, prng())); |
| } else { |
| Base::Mutate(val, prng, only_shrink); |
| } |
| } |
| }; |
| |
| void TestOneInput(const std::vector<uint8_t>& data) { |
| LLVMFuzzerTestOneInput(data.data(), data.size()); |
| } |
| |
| FUZZ_TEST(LLVMFuzzer, TestOneInput) |
| .WithDomains(ArbitraryByteVector() |
| .WithDictionary(ReadByteArrayDictionaryFromFile) |
| .WithSeeds(ReadByteArraysFromDirectory)); |