-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[libc] add hashtable fuzzing #87949
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[libc] add hashtable fuzzing #87949
Changes from all commits
6624d84
395b76d
2fef414
8c14a30
ea09412
184fbd4
7bef0dc
d5b7e26
5a28733
d632fba
6b6f64a
484d9f3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
//===-- hashtable_fuzz.cpp ------------------------------------------------===// | ||
// | ||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
// See https://llvm.org/LICENSE.txt for license information. | ||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
// | ||
//===----------------------------------------------------------------------===// | ||
/// | ||
/// Fuzzing test for llvm-libc hashtable implementations. | ||
/// | ||
//===----------------------------------------------------------------------===// | ||
#include "include/llvm-libc-types/ENTRY.h" | ||
#include "src/__support/CPP/string_view.h" | ||
#include "src/__support/HashTable/table.h" | ||
|
||
namespace LIBC_NAMESPACE { | ||
|
||
// A fuzzing payload starts with | ||
// - uint16_t: initial capacity for table A | ||
// - uint64_t: seed for table A | ||
// - uint16_t: initial capacity for table B | ||
// - uint64_t: seed for table B | ||
// Followed by a sequence of actions: | ||
// - CrossCheck: only a single byte valued (4 mod 5) | ||
// - Find: a single byte valued (3 mod 5) followed by a null-terminated string | ||
// - Insert: a single byte valued (0,1,2 mod 5) followed by a null-terminated | ||
// string | ||
static constexpr size_t INITIAL_HEADER_SIZE = | ||
2 * (sizeof(uint16_t) + sizeof(uint64_t)); | ||
extern "C" size_t LLVMFuzzerMutate(uint8_t *data, size_t size, size_t max_size); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been looking into moving to https://github.com/google/fuzztest as the fuzzing framework for our fuzz tests in future. It seems like this might be easier with that framework, if you are interested in trying it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume it is using structural unit test; so yes, it should be much more easier to approach. |
||
extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *data, size_t size, | ||
size_t max_size, unsigned int seed) { | ||
size = LLVMFuzzerMutate(data, size, max_size); | ||
// not enough to read the initial capacities and seeds | ||
if (size < INITIAL_HEADER_SIZE) | ||
return 0; | ||
|
||
// skip the initial capacities and seeds | ||
size_t i = INITIAL_HEADER_SIZE; | ||
while (i < size) { | ||
// cross check | ||
if (static_cast<uint8_t>(data[i]) % 5 == 4) { | ||
// skip the cross check byte | ||
++i; | ||
continue; | ||
} | ||
|
||
// find or insert | ||
// check if there is enough space for the action byte and the | ||
// null-terminator | ||
if (i + 2 >= max_size) | ||
return i; | ||
// skip the action byte | ||
++i; | ||
// skip the null-terminated string | ||
while (i < max_size && data[i] != 0) | ||
++i; | ||
// in the case the string is not null-terminated, null-terminate it | ||
if (i == max_size && data[i - 1] != 0) { | ||
data[i - 1] = 0; | ||
return max_size; | ||
} | ||
|
||
// move to the next action | ||
++i; | ||
} | ||
// return the new size | ||
return i; | ||
} | ||
|
||
// a tagged union | ||
struct Action { | ||
enum class Tag { Find, Insert, CrossCheck } tag; | ||
cpp::string_view key; | ||
}; | ||
|
||
static struct { | ||
size_t remaining; | ||
const char *buffer; | ||
|
||
template <typename T> T next() { | ||
static_assert(cpp::is_integral<T>::value, "T must be an integral type"); | ||
union { | ||
T result; | ||
char data[sizeof(T)]; | ||
}; | ||
for (size_t i = 0; i < sizeof(result); i++) | ||
data[i] = buffer[i]; | ||
buffer += sizeof(result); | ||
remaining -= sizeof(result); | ||
return result; | ||
} | ||
|
||
cpp::string_view next_string() { | ||
cpp::string_view result(buffer); | ||
buffer = result.end() + 1; | ||
remaining -= result.size() + 1; | ||
return result; | ||
} | ||
|
||
Action next_action() { | ||
uint8_t byte = next<uint8_t>(); | ||
switch (byte % 5) { | ||
case 4: | ||
return {Action::Tag::CrossCheck, {}}; | ||
case 3: | ||
return {Action::Tag::Find, next_string()}; | ||
default: | ||
return {Action::Tag::Insert, next_string()}; | ||
} | ||
} | ||
} global_status; | ||
|
||
class HashTable { | ||
internal::HashTable *table; | ||
|
||
public: | ||
HashTable(uint64_t size, uint64_t seed) | ||
: table(internal::HashTable::allocate(size, seed)) {} | ||
HashTable(internal::HashTable *table) : table(table) {} | ||
~HashTable() { internal::HashTable::deallocate(table); } | ||
HashTable(HashTable &&other) : table(other.table) { other.table = nullptr; } | ||
bool is_valid() const { return table != nullptr; } | ||
ENTRY *find(const char *key) { return table->find(key); } | ||
ENTRY *insert(const ENTRY &entry) { | ||
return internal::HashTable::insert(this->table, entry); | ||
} | ||
using iterator = internal::HashTable::iterator; | ||
iterator begin() const { return table->begin(); } | ||
iterator end() const { return table->end(); } | ||
}; | ||
|
||
HashTable next_hashtable() { | ||
size_t size = global_status.next<uint16_t>(); | ||
uint64_t seed = global_status.next<uint64_t>(); | ||
return HashTable(size, seed); | ||
} | ||
|
||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { | ||
global_status.buffer = reinterpret_cast<const char *>(data); | ||
global_status.remaining = size; | ||
if (global_status.remaining < INITIAL_HEADER_SIZE) | ||
return 0; | ||
|
||
HashTable table_a = next_hashtable(); | ||
HashTable table_b = next_hashtable(); | ||
for (;;) { | ||
if (global_status.remaining == 0) | ||
break; | ||
Action action = global_status.next_action(); | ||
switch (action.tag) { | ||
case Action::Tag::Find: { | ||
if (static_cast<bool>(table_a.find(action.key.data())) != | ||
static_cast<bool>(table_b.find(action.key.data()))) | ||
__builtin_trap(); | ||
break; | ||
} | ||
case Action::Tag::Insert: { | ||
char *ptr = const_cast<char *>(action.key.data()); | ||
ENTRY *a = table_a.insert(ENTRY{ptr, ptr}); | ||
ENTRY *b = table_b.insert(ENTRY{ptr, ptr}); | ||
if (a->data != b->data) | ||
__builtin_trap(); | ||
break; | ||
} | ||
case Action::Tag::CrossCheck: { | ||
for (ENTRY a : table_a) | ||
if (const ENTRY *b = table_b.find(a.key); a.data != b->data) | ||
__builtin_trap(); | ||
|
||
for (ENTRY b : table_b) | ||
if (const ENTRY *a = table_a.find(b.key); a->data != b.data) | ||
__builtin_trap(); | ||
|
||
break; | ||
} | ||
} | ||
} | ||
return 0; | ||
} | ||
|
||
} // namespace LIBC_NAMESPACE |
Uh oh!
There was an error while loading. Please reload this page.