From 14dce5c17c2b2993ffd4fa456524b781f46a2dc6 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Fri, 24 Mar 2017 11:18:52 +0100 Subject: Bug 1348775 - Add custom TLS mutators for libFuzzer fuzzing targets r=franziskus Differential Revision: https://nss-review.dev.mozaws.net/D261 --- fuzz/fuzz.gyp | 56 +++++----- fuzz/pkcs8_target.cc | 10 +- fuzz/quickder_target.cc | 10 +- fuzz/shared.cc | 18 +++ fuzz/shared.h | 14 +-- fuzz/tls_client_target.cc | 18 ++- fuzz/tls_mutators.cc | 276 ++++++++++++++++++++++++++++++++++++++++++++++ fuzz/tls_mutators.h | 23 ++++ fuzz/tls_server_target.cc | 18 ++- 9 files changed, 390 insertions(+), 53 deletions(-) create mode 100644 fuzz/shared.cc create mode 100644 fuzz/tls_mutators.cc create mode 100644 fuzz/tls_mutators.h (limited to 'fuzz') diff --git a/fuzz/fuzz.gyp b/fuzz/fuzz.gyp index 180f08465..da52d6e37 100644 --- a/fuzz/fuzz.gyp +++ b/fuzz/fuzz.gyp @@ -25,7 +25,12 @@ 'targets': [ { 'target_name': 'fuzz_base', + 'type': 'static_library', + 'sources': [ + 'shared.cc', + ], 'dependencies': [ + '<(DEPTH)/exports.gyp:nss_exports', '<(DEPTH)/lib/certdb/certdb.gyp:certdb', '<(DEPTH)/lib/certhigh/certhigh.gyp:certhi', '<(DEPTH)/lib/cryptohi/cryptohi.gyp:cryptohi', @@ -41,7 +46,6 @@ ], 'conditions': [ ['fuzz_oss==0', { - 'type': 'static_library', 'sources': [ ' idist(0, mutators.size() - 1); + return mutators.at(idist(rng))(data, size, max_size, seed); + } + + return LLVMFuzzerMutate(data, size, max_size); +} diff --git a/fuzz/shared.h b/fuzz/shared.h index 24ca51f85..35621eb9d 100644 --- a/fuzz/shared.h +++ b/fuzz/shared.h @@ -24,17 +24,7 @@ class NSSDatabase { typedef std::vector Mutators; -size_t CustomMutate(Mutators &mutators, uint8_t *Data, size_t Size, - size_t MaxSize, unsigned int Seed) { - std::mt19937 rng(Seed); - static std::bernoulli_distribution bdist; - - if (bdist(rng)) { - std::uniform_int_distribution idist(0, mutators.size() - 1); - return mutators.at(idist(rng))(Data, Size, MaxSize, Seed); - } - - return LLVMFuzzerMutate(Data, Size, MaxSize); -} +size_t CustomMutate(Mutators mutators, uint8_t *data, size_t size, + size_t max_size, unsigned int seed); #endif // shared_h__ diff --git a/fuzz/tls_client_target.cc b/fuzz/tls_client_target.cc index 4b40257a9..82e4a5dfb 100644 --- a/fuzz/tls_client_target.cc +++ b/fuzz/tls_client_target.cc @@ -13,6 +13,7 @@ #include "shared.h" #include "tls_client_config.h" #include "tls_common.h" +#include "tls_mutators.h" #include "tls_socket.h" static SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checksig, @@ -79,10 +80,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t len) { // Clear the cache. We never want to resume as we couldn't reproduce that. SSL_ClearSessionCache(); -#ifdef UNSAFE_FUZZER_MODE // Reset the RNG state. assert(RNG_RandomUpdate(NULL, 0) == SECSuccess); -#endif // Create and import dummy socket. std::unique_ptr socket(new DummyPrSocket(data, len)); @@ -101,3 +100,18 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t len) { return 0; } + +extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, + size_t max_size, unsigned int seed) { + return CustomMutate({TlsMutatorDropRecord, TlsMutatorShuffleRecords, + TlsMutatorDuplicateRecord, TlsMutatorTruncateRecord, + TlsMutatorFragmentRecord}, + data, size, max_size, seed); +} + +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) { + return TlsCrossOver(data1, size1, data2, size2, out, max_out_size, seed); +} diff --git a/fuzz/tls_mutators.cc b/fuzz/tls_mutators.cc new file mode 100644 index 000000000..ccf1935bf --- /dev/null +++ b/fuzz/tls_mutators.cc @@ -0,0 +1,276 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "shared.h" +#include "tls_parser.h" + +#include "ssl.h" +#include "sslimpl.h" + +using namespace nss_test; + +// Helper class to simplify TLS record manipulation. +class Record { + public: + static std::unique_ptr Create(const uint8_t *data, size_t size, + size_t remaining) { + return std::unique_ptr(new Record(data, size, remaining)); + } + + void insert_before(const std::unique_ptr &other) { + assert(data_ && size_ > 0); + + // Copy data in case other == this. + uint8_t buf[size_]; + memcpy(buf, data_, size_); + + uint8_t *dest = const_cast(other->data()); + // Make room for the record we want to insert. + memmove(dest + size_, other->data(), other->size() + other->remaining()); + // Insert the record. + memcpy(dest, buf, size_); + } + + void truncate(size_t length) { + assert(length >= 5); + uint8_t *dest = const_cast(data_); + (void)ssl_EncodeUintX(length - 5, 2, &dest[3]); + memmove(dest + length, data_ + size_, remaining_); + } + + void drop() { + uint8_t *dest = const_cast(data_); + memmove(dest, data_ + size_, remaining_); + } + + const uint8_t *data() { return data_; } + size_t remaining() { return remaining_; } + size_t size() { return size_; } + + private: + Record(const uint8_t *data, size_t size, size_t remaining) + : data_(data), remaining_(remaining), size_(size) {} + + const uint8_t *data_; + size_t remaining_; + size_t size_; +}; + +// Parse records contained in a given TLS transcript. +std::vector> ParseRecords(const uint8_t *data, + size_t size) { + std::vector> records; + TlsParser parser(data, size); + + while (parser.remaining()) { + size_t offset = parser.consumed(); + + uint32_t type; + if (!parser.Read(&type, 1)) { + break; + } + + uint32_t version; + if (!parser.Read(&version, 2)) { + break; + } + + DataBuffer fragment; + if (!parser.ReadVariable(&fragment, 2)) { + break; + } + + records.push_back( + Record::Create(data + offset, fragment.len() + 5, parser.remaining())); + } + + return records; +} + +// Mutator that drops whole TLS records. +size_t TlsMutatorDropRecord(uint8_t *data, size_t size, size_t max_size, + unsigned int seed) { + std::mt19937 rng(seed); + + // Find TLS records in the corpus. + auto records = ParseRecords(data, size); + if (records.empty()) { + return 0; + } + + // Pick a record to drop at random. + std::uniform_int_distribution dist(0, records.size() - 1); + auto &rec = records.at(dist(rng)); + + // Drop the record. + rec->drop(); + + // Return the new final size. + return size - rec->size(); +} + +// Mutator that shuffles TLS records in a transcript. +size_t TlsMutatorShuffleRecords(uint8_t *data, size_t size, size_t max_size, + unsigned int seed) { + std::mt19937 rng(seed); + + // Store the original corpus. + uint8_t buf[size]; + memcpy(buf, data, size); + + // Find TLS records in the corpus. + auto records = ParseRecords(buf, sizeof(buf)); + if (records.empty()) { + return 0; + } + + // Find offset of first record in target buffer. + uint8_t *dest = const_cast(ParseRecords(data, size).at(0)->data()); + + // Shuffle record order. + std::shuffle(records.begin(), records.end(), rng); + + // Write records to their new positions. + for (auto &rec : records) { + memcpy(dest, rec->data(), rec->size()); + dest += rec->size(); + } + + // Final size hasn't changed. + return size; +} + +// Mutator that duplicates a single TLS record and randomly inserts it. +size_t TlsMutatorDuplicateRecord(uint8_t *data, size_t size, size_t max_size, + unsigned int seed) { + std::mt19937 rng(seed); + + // Find TLS records in the corpus. + const auto records = ParseRecords(data, size); + if (records.empty()) { + return 0; + } + + // Pick a record to duplicate at random. + std::uniform_int_distribution dist(0, records.size() - 1); + auto &rec = records.at(dist(rng)); + if (size + rec->size() > max_size) { + return 0; + } + + // Insert before random record. + rec->insert_before(records.at(dist(rng))); + + // Return the new final size. + return size + rec->size(); +} + +// Mutator that truncates a TLS record. +size_t TlsMutatorTruncateRecord(uint8_t *data, size_t size, size_t max_size, + unsigned int seed) { + std::mt19937 rng(seed); + + // Find TLS records in the corpus. + const auto records = ParseRecords(data, size); + if (records.empty()) { + return 0; + } + + // Pick a record to truncate at random. + std::uniform_int_distribution dist(0, records.size() - 1); + auto &rec = records.at(dist(rng)); + + // Need a record with data. + if (rec->size() <= 5) { + return 0; + } + + // Truncate. + std::uniform_int_distribution dist2(5, rec->size() - 1); + size_t new_length = dist2(rng); + rec->truncate(new_length); + + // Return the new final size. + return size + new_length - rec->size(); +} + +// Mutator that splits a TLS record in two. +size_t TlsMutatorFragmentRecord(uint8_t *data, size_t size, size_t max_size, + unsigned int seed) { + std::mt19937 rng(seed); + + if (size + 5 > max_size) { + return 0; + } + + // Find TLS records in the corpus. + const auto records = ParseRecords(data, size); + if (records.empty()) { + return 0; + } + + // Pick a record to fragment at random. + std::uniform_int_distribution dist(0, records.size() - 1); + auto &rec = records.at(dist(rng)); + uint8_t *rdata = const_cast(rec->data()); + size_t length = rec->size(); + size_t content_length = length - 5; + + if (content_length < 2) { + return 0; + } + + // Assign a new length to the first fragment. + size_t new_length = content_length / 2; + uint8_t *content = ssl_EncodeUintX(new_length, 2, &rdata[3]); + + // Make room for one more header. + memmove(content + new_length + 5, content + new_length, + rec->remaining() + content_length - new_length); + + // Write second header. + memcpy(content + new_length, rdata, 3); + (void)ssl_EncodeUintX(content_length - new_length, 2, + &content[new_length + 3]); + + return size + 5; +} + +// Cross-over function that merges and shuffles two transcripts. +size_t TlsCrossOver(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::mt19937 rng(seed); + + // Find TLS records in the corpus. + auto records1 = ParseRecords(data1, size1); + if (records1.empty()) { + return 0; + } + + { // Merge the two vectors. + auto records2 = ParseRecords(data2, size2); + if (records2.empty()) { + return 0; + } + std::move(records2.begin(), records2.end(), std::back_inserter(records1)); + } + + // Shuffle record order. + std::shuffle(records1.begin(), records1.end(), rng); + + size_t total = 0; + for (auto &rec : records1) { + size_t length = rec->size(); + if (total + length > max_out_size) { + break; + } + + // Write record to its new position. + memcpy(out + total, rec->data(), length); + total += length; + } + + return total; +} diff --git a/fuzz/tls_mutators.h b/fuzz/tls_mutators.h new file mode 100644 index 000000000..48429626f --- /dev/null +++ b/fuzz/tls_mutators.h @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef tls_mutators_h__ +#define tls_mutators_h__ + +size_t TlsMutatorDropRecord(uint8_t *data, size_t size, size_t max_size, + unsigned int seed); +size_t TlsMutatorShuffleRecords(uint8_t *data, size_t size, size_t max_size, + unsigned int seed); +size_t TlsMutatorDuplicateRecord(uint8_t *data, size_t size, size_t max_size, + unsigned int seed); +size_t TlsMutatorTruncateRecord(uint8_t *data, size_t size, size_t max_size, + unsigned int seed); +size_t TlsMutatorFragmentRecord(uint8_t *data, size_t size, size_t max_size, + unsigned int seed); + +size_t TlsCrossOver(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); + +#endif // tls_mutators_h__ diff --git a/fuzz/tls_server_target.cc b/fuzz/tls_server_target.cc index 9a533fb13..67193368f 100644 --- a/fuzz/tls_server_target.cc +++ b/fuzz/tls_server_target.cc @@ -12,6 +12,7 @@ #include "shared.h" #include "tls_common.h" +#include "tls_mutators.h" #include "tls_server_certs.h" #include "tls_server_config.h" #include "tls_socket.h" @@ -83,10 +84,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t len) { // Clear the cache. We never want to resume as we couldn't reproduce that. SSL_ClearSessionCache(); -#ifdef UNSAFE_FUZZER_MODE // Reset the RNG state. assert(RNG_RandomUpdate(NULL, 0) == SECSuccess); -#endif // Create model socket. static ScopedPRFileDesc model(SSL_ImportFD(nullptr, PR_NewTCPSocket())); @@ -108,3 +107,18 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t len) { return 0; } + +extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, + size_t max_size, unsigned int seed) { + return CustomMutate({TlsMutatorDropRecord, TlsMutatorShuffleRecords, + TlsMutatorDuplicateRecord, TlsMutatorTruncateRecord, + TlsMutatorFragmentRecord}, + data, size, max_size, seed); +} + +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) { + return TlsCrossOver(data1, size1, data2, size2, out, max_out_size, seed); +} -- cgit v1.2.1