| // Copyright 2020 The Pigweed Authors |
| // |
| // 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 |
| // |
| // https://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. |
| |
| // This file implements a basic fuzz test for the TokenDatabase class |
| // A database is created from fuzz data, and a random entry count (also |
| // derived from the fuzz data) is set. We then run iterations and 'find' |
| // operations on this database. |
| |
| #include <cstring> |
| |
| #include "pw_fuzzer/asan_interface.h" |
| #include "pw_fuzzer/fuzzed_data_provider.h" |
| #include "pw_preprocessor/util.h" |
| #include "pw_span/span.h" |
| #include "pw_tokenizer/token_database.h" |
| |
| namespace pw::tokenizer { |
| namespace { |
| |
| enum FuzzTestType : uint8_t { |
| kValidHeader, |
| kRandomHeader, |
| kMaxValue = kRandomHeader, |
| }; |
| |
| constexpr size_t kTokenHeaderSize = 16; |
| |
| // The default max length in bytes of fuzzed data provided. Note that |
| // this needs to change if the fuzzer executable is run with a |
| // '-max_len' argument. |
| constexpr size_t kFuzzDataSizeMax = 4096; |
| |
| // Location of the 'EntryCount' field in the token header. |
| constexpr size_t kEntryCountOffset = 8; |
| constexpr size_t kEntryCountSize = 4; |
| |
| void SetTokenEntryCountInBuffer(uint8_t* buffer, uint32_t count) { |
| memcpy(buffer + kEntryCountOffset, &count, kEntryCountSize); |
| } |
| |
| void IterateOverDatabase(TokenDatabase* const database) { |
| for (TokenDatabase::Entry entry : *database) { |
| // Since we don't "use" the contents of the entry, we exercise |
| // the entry by extracting its contents into volatile variables |
| // to prevent it from being optimized out during compilation. |
| [[maybe_unused]] volatile const char* entry_string = entry.string; |
| [[maybe_unused]] volatile uint32_t entry_token = entry.token; |
| } |
| } |
| |
| } // namespace |
| |
| extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
| constexpr size_t kBufferSizeMax = kFuzzDataSizeMax + kTokenHeaderSize; |
| constexpr char kDefaultHeader[] = "TOKENS\0\0\0\0\0\0\0\0\0"; |
| static uint8_t buffer[kBufferSizeMax]; |
| |
| if (size > kFuzzDataSizeMax) { |
| return 0; |
| } |
| |
| FuzzedDataProvider provider(data, size); |
| |
| // Initialize the token header with either a valid or invalid header |
| // based on a random enum consumed from the fuzz data. |
| switch (provider.ConsumeEnum<FuzzTestType>()) { |
| case kValidHeader: |
| memcpy(buffer, kDefaultHeader, kTokenHeaderSize); |
| break; |
| |
| case kRandomHeader: { |
| std::vector<uint8_t> random_header = |
| provider.ConsumeBytes<uint8_t>(kTokenHeaderSize); |
| random_header.resize(kTokenHeaderSize); |
| memcpy(buffer, &random_header[0], kTokenHeaderSize); |
| break; |
| } |
| } |
| |
| // Consume a 'test token' integer to look up later in the database. |
| uint32_t random_token = provider.ConsumeIntegral<uint32_t>(); |
| |
| // Consume a 'token count' integer to set as our database entry count. |
| uint32_t random_token_count = |
| provider.ConsumeIntegralInRange<uint32_t>(0, kFuzzDataSizeMax); |
| |
| // Consume the remaining data. Note that the data corresponding to the |
| // string entries in the database are not explicitly null-terminated. |
| // TODO(karthikmb): Once OSS-Fuzz updates to Clang11.0, switch to |
| // provider.ConsumeData() to avoid extra memory and the memcpy call. |
| auto consumed_bytes = |
| provider.ConsumeBytes<uint8_t>(provider.remaining_bytes()); |
| memcpy(buffer + kTokenHeaderSize, &consumed_bytes[0], consumed_bytes.size()); |
| |
| SetTokenEntryCountInBuffer(buffer, random_token_count); |
| |
| // Poison the unused buffer space for this run of the fuzzer to |
| // prevent the token database creator from reading too far in. |
| size_t data_size = kTokenHeaderSize + consumed_bytes.size(); |
| size_t poisoned_length = kBufferSizeMax - data_size; |
| void* poisoned = &buffer[data_size]; |
| |
| ASAN_POISON_MEMORY_REGION(poisoned, poisoned_length); |
| |
| // We create a database from a span of the buffer since the string |
| // entries might not be null terminated, and the creation of a database |
| // from a raw buffer has an explicit null terminated string requirement |
| // specified in the API. |
| span<uint8_t> data_span(buffer, data_size); |
| auto token_database = TokenDatabase::Create<span<uint8_t>>(data_span); |
| [[maybe_unused]] volatile auto match = token_database.Find(random_token); |
| |
| IterateOverDatabase(&token_database); |
| |
| // Un-poison for the next iteration. |
| ASAN_UNPOISON_MEMORY_REGION(poisoned, poisoned_length); |
| |
| return 0; |
| } |
| |
| } // namespace pw::tokenizer |