diff --git a/.arclint b/.arclint index 812825a28..d225d8fc2 100644 --- a/.arclint +++ b/.arclint @@ -1,263 +1,264 @@ { "linters": { "generated": { "type": "generated" }, "clang-format": { "type": "clang-format", "version": ">=8.0", "bin": ["clang-format-8", "clang-format"], "include": "(^src/.*\\.(h|c|cpp|mm)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)" ] }, "autopep8": { "type": "autopep8", "version": ">=1.3.4", "include": "(\\.py$)", "exclude": [ "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)" ], "flags": [ "--aggressive", "--ignore=W503,W504" ] }, "flake8": { "type": "flake8", "version": ">=3.0", "include": "(\\.py$)", "exclude": [ "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)" ], "flags": [ "--ignore=E501,E704,W503,W504" ] }, "lint-format-strings": { "type": "lint-format-strings", "include": "(^src/.*\\.(h|c|cpp)$)", "exclude": [ - "(^src/(secp256k1|univalue|leveldb)/)" + "(^src/(secp256k1|univalue|leveldb)/)", + "(^src/test/fuzz/strprintf.cpp$)" ] }, "check-doc": { "type": "check-doc", "include": "(^src/.*\\.(h|c|cpp)$)" }, "lint-tests": { "type": "lint-tests", "include": "(^src/(seeder/|rpc/|wallet/)?test/.*\\.(cpp)$)" }, "lint-python-format": { "type": "lint-python-format", "include": "(\\.py$)", "exclude": [ "(^test/lint/lint-python-format\\.py$)", "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)" ] }, "phpcs": { "type": "phpcs", "include": "(\\.php$)", "exclude": [ "(^arcanist/__phutil_library_.+\\.php$)" ], "phpcs.standard": "arcanist/phpcs.xml" }, "lint-locale-dependence": { "type": "lint-locale-dependence", "include": "(^src/.*\\.(h|cpp)$)", "exclude": [ "(^src/(crypto/ctaes/|leveldb/|secp256k1/|tinyformat.h|univalue/))" ] }, "lint-cheader": { "type": "lint-cheader", "include": "(^src/.*\\.(h|cpp)$)", "exclude": [ "(^src/(crypto/ctaes|secp256k1|univalue|leveldb)/)" ] }, "spelling": { "type": "spelling", "exclude": [ "(^build-aux/m4/)", "(^depends/)", "(^doc/release-notes/)", "(^contrib/gitian-builder/)", "(^src/(qt/locale|secp256k1|univalue|leveldb)/)", "(^test/lint/dictionary/)" ], "spelling.dictionaries": [ "test/lint/dictionary/english.json" ] }, "lint-assert-with-side-effects": { "type": "lint-assert-with-side-effects", "include": "(^src/.*\\.(h|cpp)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)" ] }, "lint-include-quotes": { "type": "lint-include-quotes", "include": "(^src/.*\\.(h|cpp)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)" ] }, "lint-include-guard": { "type": "lint-include-guard", "include": "(^src/.*\\.h$)", "exclude": [ "(^src/(crypto/ctaes|secp256k1|univalue|leveldb)/)", "(^src/tinyformat.h$)" ] }, "lint-include-source": { "type": "lint-include-source", "include": "(^src/.*\\.(h|c|cpp)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)" ] }, "lint-stdint": { "type": "lint-stdint", "include": "(^src/.*\\.(h|c|cpp)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)", "(^src/compat/assumptions.h$)" ] }, "lint-source-filename": { "type": "lint-source-filename", "include": "(^src/.*\\.(h|c|cpp)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)" ] }, "lint-boost-dependencies": { "type": "lint-boost-dependencies", "include": "(^src/.*\\.(h|cpp)$)" }, "check-rpc-mappings": { "type": "check-rpc-mappings", "include": "(^src/(rpc/|wallet/rpc).*\\.cpp$)" }, "lint-python-encoding": { "type": "lint-python-encoding", "include": "(\\.py$)", "exclude": [ "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)" ] }, "lint-python-shebang": { "type": "lint-python-shebang", "include": "(\\.py$)", "exclude": [ "(__init__\\.py$)", "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)" ] }, "lint-bash-shebang": { "type": "lint-bash-shebang", "include": "(\\.sh$)", "exclude": [ "(^contrib/gitian-builder/)" ] }, "shellcheck": { "type": "shellcheck", "version": ">=0.7.0", "flags": [ "--external-sources", "--source-path=SCRIPTDIR" ], "include": "(\\.sh$)", "exclude": [ "(^contrib/gitian-builder/)", "(^src/(secp256k1|univalue)/)" ] }, "lint-shell-locale": { "type": "lint-shell-locale", "include": "(\\.sh$)", "exclude": [ "(^contrib/gitian-builder/)", "(^src/(secp256k1|univalue)/)", "(^cmake/utils/log-and-print-on-failure.sh)" ] }, "lint-cpp-void-parameters": { "type": "lint-cpp-void-parameters", "include": "(^src/.*\\.(h|cpp)$)", "exclude": [ "(^src/(crypto/ctaes|secp256k1|univalue|leveldb)/)", "(^src/compat/glibc_compat.cpp$)" ] }, "lint-logs": { "type": "lint-logs", "include": "(^src/.*\\.(h|cpp)$)" }, "lint-qt": { "type": "lint-qt", "include": "(^src/qt/.*\\.(h|cpp)$)", "exclude": [ "(^src/qt/(locale|forms|res)/)" ] }, "lint-doxygen": { "type": "lint-doxygen", "include": "(^src/.*\\.(h|c|cpp)$)", "exclude": [ "(^src/(crypto/ctaes|secp256k1|univalue|leveldb)/)" ] }, "lint-whitespace": { "type": "lint-whitespace", "include": "(\\.(ac|am|cmake|conf|in|include|json|m4|md|openrc|php|pl|sh|txt|yml)$)", "exclude": [ "(^contrib/gitian-builder/)", "(^src/(secp256k1|univalue|leveldb)/)" ] }, "lint-cppcheck": { "type": "lint-cppcheck", "include": "(^src/.*\\.(h|c|cpp)$)", "exclude": [ "(^src/(crypto/ctaes|secp256k1|univalue|leveldb)/)" ] }, "yamllint": { "type": "yamllint", "include": "(\\.(yml|yaml)$)" }, "lint-check-nonfatal": { "type": "lint-check-nonfatal", "include": [ "(^src/rpc/.*\\.(h|c|cpp)$)", "(^src/wallet/rpc*.*\\.(h|c|cpp)$)" ], "exclude": "(^src/rpc/server.cpp)" }, "lint-markdown": { "type": "lint-markdown", "include": [ "(\\.md$)" ], "exclude": "(^contrib/gitian-builder/)" }, "lint-python-mutable-default": { "type": "lint-python-mutable-default", "include": "(\\.py$)" } } } diff --git a/src/test/fuzz/CMakeLists.txt b/src/test/fuzz/CMakeLists.txt index f88079005..a26e45dea 100644 --- a/src/test/fuzz/CMakeLists.txt +++ b/src/test/fuzz/CMakeLists.txt @@ -1,102 +1,103 @@ # Fuzzer test harness add_custom_target(bitcoin-fuzzers) macro(add_fuzz_target TARGET EXE_NAME) add_executable(${TARGET} EXCLUDE_FROM_ALL fuzz.cpp ${ARGN} ) set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME ${EXE_NAME}) target_link_libraries(${TARGET} server testutil) add_dependencies(bitcoin-fuzzers ${TARGET}) endmacro() function(add_regular_fuzz_targets) foreach(_fuzz_test_name ${ARGN}) sanitize_target_name("fuzz-" ${_fuzz_test_name} _fuzz_target_name) add_fuzz_target( ${_fuzz_target_name} ${_fuzz_test_name} # Sources "${_fuzz_test_name}.cpp" ) endforeach() endfunction() include(SanitizeHelper) function(add_deserialize_fuzz_targets) foreach(_fuzz_test_name ${ARGN}) sanitize_target_name("fuzz-" ${_fuzz_test_name} _fuzz_target_name) add_fuzz_target( ${_fuzz_target_name} ${_fuzz_test_name} # Sources deserialize.cpp ) sanitize_c_cxx_definition("" ${_fuzz_test_name} _target_definition) string(TOUPPER ${_target_definition} _target_definition) target_compile_definitions(${_fuzz_target_name} PRIVATE ${_target_definition}) endforeach() endfunction() add_regular_fuzz_targets( addrdb bloom_filter rolling_bloom_filter cashaddr descriptor_parse eval_script net_permissions parse_iso8601 psbt script script_flags spanparsing + strprintf timedata transaction ) add_deserialize_fuzz_targets( addr_info_deserialize address_deserialize addrman_deserialize banentry_deserialize block_deserialize block_file_info_deserialize block_filter_deserialize block_header_and_short_txids_deserialize blockheader_deserialize blocklocator_deserialize blockmerkleroot blocktransactions_deserialize blocktransactionsrequest_deserialize blockundo_deserialize bloomfilter_deserialize coins_deserialize diskblockindex_deserialize fee_rate_deserialize flat_file_pos_deserialize inv_deserialize key_origin_info_deserialize merkle_block_deserialize messageheader_deserialize netaddr_deserialize out_point_deserialize partial_merkle_tree_deserialize partially_signed_transaction_deserialize prefilled_transaction_deserialize psbt_input_deserialize psbt_output_deserialize pub_key_deserialize script_deserialize service_deserialize sub_net_deserialize tx_in_deserialize txoutcompressor_deserialize txundo_deserialize ) diff --git a/src/test/fuzz/FuzzedDataProvider.h b/src/test/fuzz/FuzzedDataProvider.h index 9d4044bf9..f4e376268 100644 --- a/src/test/fuzz/FuzzedDataProvider.h +++ b/src/test/fuzz/FuzzedDataProvider.h @@ -1,249 +1,326 @@ //===- FuzzedDataProvider.h - Utility header for fuzz targets ---*- C++ -* ===// // // 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 // //===----------------------------------------------------------------------===// // A single header library providing an utility class to break up an array of // bytes. Whenever run on the same input, provides the same output, as long as // its methods are called in the same order, with the same arguments. //===----------------------------------------------------------------------===// #ifndef LLVM_FUZZER_FUZZED_DATA_PROVIDER_H_ #define LLVM_FUZZER_FUZZED_DATA_PROVIDER_H_ +#include #include #include #include - -#include #include #include #include #include #include #include +// In addition to the comments below, the API is also briefly documented at +// https://github.com/google/fuzzing/blob/master/docs/split-inputs.md#fuzzed-data-provider class FuzzedDataProvider { public: // |data| is an array of length |size| that the FuzzedDataProvider wraps to // provide more granular access. |data| must outlive the FuzzedDataProvider. FuzzedDataProvider(const uint8_t *data, size_t size) : data_ptr_(data), remaining_bytes_(size) {} ~FuzzedDataProvider() = default; // Returns a std::vector containing |num_bytes| of input data. If fewer than // |num_bytes| of data remain, returns a shorter std::vector containing all // of the data that's left. Can be used with any byte sized type, such as // char, uint8_t, uint8_t, etc. template std::vector ConsumeBytes(size_t num_bytes) { num_bytes = std::min(num_bytes, remaining_bytes_); return ConsumeBytes(num_bytes, num_bytes); } // Similar to |ConsumeBytes|, but also appends the terminator value at the // end of the resulting vector. Useful, when a mutable null-terminated // C-string is needed, for example. But that is a rare case. Better avoid // it, if possible, and prefer using |ConsumeBytes| or // |ConsumeBytesAsString| methods. template std::vector ConsumeBytesWithTerminator(size_t num_bytes, T terminator = 0) { num_bytes = std::min(num_bytes, remaining_bytes_); std::vector result = ConsumeBytes(num_bytes + 1, num_bytes); result.back() = terminator; return result; } // Returns a std::string containing |num_bytes| of input data. Using this // and // |.c_str()| on the resulting string is the best way to get an immutable // null-terminated C string. If fewer than |num_bytes| of data remain, // returns a shorter std::string containing all of the data that's left. std::string ConsumeBytesAsString(size_t num_bytes) { static_assert( sizeof(std::string::value_type) == sizeof(uint8_t), "ConsumeBytesAsString cannot convert the data to a string."); num_bytes = std::min(num_bytes, remaining_bytes_); std::string result( reinterpret_cast(data_ptr_), num_bytes); Advance(num_bytes); return result; } // Returns a number in the range [min, max] by consuming bytes from the // input data. The value might not be uniformly distributed in the given // range. If there's no input data left, always returns |min|. |min| must // be less than or equal to |max|. template T ConsumeIntegralInRange(T min, T max) { static_assert(std::is_integral::value, "An integral type is required."); static_assert(sizeof(T) <= sizeof(uint64_t), "Unsupported integral type."); - if (min > max) abort(); + if (min > max) { + abort(); + } // Use the biggest type possible to hold the range and the result. uint64_t range = static_cast(max) - min; uint64_t result = 0; size_t offset = 0; while (offset < sizeof(T) * CHAR_BIT && (range >> offset) > 0 && remaining_bytes_ != 0) { // Pull bytes off the end of the seed data. Experimentally, this // seems to allow the fuzzer to more easily explore the input space. // This makes sense, since it works by modifying inputs that caused // new code to run, and this data is often used to encode length of // data read by |ConsumeBytes|. Separating out read lengths makes it // easier modify the contents of the data that is actually read. --remaining_bytes_; result = (result << CHAR_BIT) | data_ptr_[remaining_bytes_]; offset += CHAR_BIT; } // Avoid division by 0, in case |range + 1| results in overflow. - if (range != std::numeric_limits::max()) + if (range != std::numeric_limits::max()) { result = result % (range + 1); + } return static_cast(min + result); } // Returns a std::string of length from 0 to |max_length|. When it runs out // of input data, returns what remains of the input. Designed to be more // stable with respect to a fuzzer inserting characters than just picking a // random length and then consuming that many bytes with |ConsumeBytes|. std::string ConsumeRandomLengthString(size_t max_length) { // Reads bytes from the start of |data_ptr_|. Maps "\\" to "\", and maps // "\" followed by anything else to the end of the string. As a result // of this logic, a fuzzer can insert characters into the string, and // the string will be lengthened to include those new characters, // resulting in a more stable fuzzer than picking the length of a string // independently from picking its contents. std::string result; // Reserve the anticipated capaticity to prevent several reallocations. result.reserve(std::min(max_length, remaining_bytes_)); for (size_t i = 0; i < max_length && remaining_bytes_ != 0; ++i) { char next = ConvertUnsignedToSigned(data_ptr_[0]); Advance(1); if (next == '\\' && remaining_bytes_ != 0) { next = ConvertUnsignedToSigned(data_ptr_[0]); Advance(1); - if (next != '\\') break; + if (next != '\\') { + break; + } } result += next; } result.shrink_to_fit(); return result; } // Returns a std::vector containing all remaining bytes of the input data. template std::vector ConsumeRemainingBytes() { return ConsumeBytes(remaining_bytes_); } + // Returns a std::string containing all remaining bytes of the input data. // Prefer using |ConsumeRemainingBytes| unless you actually need a // std::string object. Returns a std::vector containing all remaining bytes // of the input data. std::string ConsumeRemainingBytesAsString() { return ConsumeBytesAsString(remaining_bytes_); } // Returns a number in the range [Type's min, Type's max]. The value might // not be uniformly distributed in the given range. If there's no input data // left, always returns |min|. template T ConsumeIntegral() { return ConsumeIntegralInRange(std::numeric_limits::min(), std::numeric_limits::max()); } // Reads one byte and returns a bool, or false when no data remains. bool ConsumeBool() { return 1 & ConsumeIntegral(); } - // Returns a copy of a value selected from a fixed-size |array|. + // Returns a copy of the value selected from the given fixed-size |array|. template T PickValueInArray(const T (&array)[size]) { static_assert(size > 0, "The array must be non empty."); return array[ConsumeIntegralInRange(0, size - 1)]; } template T PickValueInArray(std::initializer_list list) { - // static_assert(list.size() > 0, "The array must be non empty."); + // Static assert won't work here: + // https://stackoverflow.com/questions/5438671/static-assert-on-initializer-listsize + if (!list.size()) { + abort(); + } + return *(list.begin() + ConsumeIntegralInRange(0, list.size() - 1)); } - // Return an enum value. The enum must start at 0 and be contiguous. It must - // also contain |kMaxValue| aliased to its largest (inclusive) value. Such - // as: enum class Foo { SomeValue, OtherValue, kMaxValue = OtherValue }; + // Returns an enum value. The enum must start at 0 and be contiguous. It + // must also contain |kMaxValue| aliased to its largest (inclusive) value. + // Such as: + // enum class Foo { SomeValue, OtherValue, kMaxValue = OtherValue }; template T ConsumeEnum() { static_assert(std::is_enum::value, "|T| must be an enum type."); return static_cast(ConsumeIntegralInRange( 0, static_cast(T::kMaxValue))); } + // Returns a floating point number in the range [0.0, 1.0]. If there's no + // input data left, always returns 0. + template T ConsumeProbability() { + static_assert(std::is_floating_point::value, + "A floating point type is required."); + + // Use different integral types for different floating point types in + // order to provide better density of the resulting values. + using IntegralType = + typename std::conditional<(sizeof(T) <= sizeof(uint32_t)), uint32_t, + uint64_t>::type; + + T result = static_cast(ConsumeIntegral()); + result /= static_cast(std::numeric_limits::max()); + return result; + } + + // Returns a floating point value in the range [Type's lowest, Type's max] + // by consuming bytes from the input data. If there's no input data left, + // always returns approximately 0. + template T ConsumeFloatingPoint() { + return ConsumeFloatingPointInRange(std::numeric_limits::lowest(), + std::numeric_limits::max()); + } + + // Returns a floating point value in the given range by consuming bytes from + // the input data. If there's no input data left, returns |min|. Note that + // |min| must be less than or equal to |max|. + template T ConsumeFloatingPointInRange(T min, T max) { + if (min > max) { + abort(); + } + + T range = .0; + T result = min; + constexpr T zero(.0); + if (max > zero && min < zero && + max > min + std::numeric_limits::max()) { + // The diff |max - min| would overflow the given floating point + // type. Use the half of the diff as the range and consume a bool to + // decide whether the result is in the first of the second part of + // the diff. + range = (max / 2.0) - (min / 2.0); + if (ConsumeBool()) { + result += range; + } + } else { + range = max - min; + } + + return result + range * ConsumeProbability(); + } + // Reports the remaining bytes available for fuzzed input. size_t remaining_bytes() { return remaining_bytes_; } private: FuzzedDataProvider(const FuzzedDataProvider &) = delete; FuzzedDataProvider &operator=(const FuzzedDataProvider &) = delete; void Advance(size_t num_bytes) { - if (num_bytes > remaining_bytes_) abort(); + if (num_bytes > remaining_bytes_) { + abort(); + } data_ptr_ += num_bytes; remaining_bytes_ -= num_bytes; } template std::vector ConsumeBytes(size_t size, size_t num_bytes_to_consume) { static_assert(sizeof(T) == sizeof(uint8_t), "Incompatible data type."); // The point of using the size-based constructor below is to increase // the odds of having a vector object with capacity being equal to the // length. That part is always implementation specific, but at least // both libc++ and libstdc++ allocate the requested number of bytes in // that constructor, which seems to be a natural choice for other // implementations as well. To increase the odds even more, we also call // |shrink_to_fit| below. std::vector result(size); + if (size == 0) { + if (num_bytes_to_consume != 0) { + abort(); + } + return result; + } + std::memcpy(result.data(), data_ptr_, num_bytes_to_consume); Advance(num_bytes_to_consume); // Even though |shrink_to_fit| is also implementation specific, we // expect it to provide an additional assurance in case vector's // constructor allocated a buffer which is larger than the actual amount // of data we put inside it. result.shrink_to_fit(); return result; } template TS ConvertUnsignedToSigned(TU value) { static_assert(sizeof(TS) == sizeof(TU), "Incompatible data types."); static_assert(!std::numeric_limits::is_signed, "Source type must be unsigned."); // TODO(Dor1s): change to `if constexpr` once C++17 becomes mainstream. - if (std::numeric_limits::is_modulo) return static_cast(value); + if (std::numeric_limits::is_modulo) { + return static_cast(value); + } // Avoid using implementation-defined unsigned to signer conversions. // To learn more, see https://stackoverflow.com/questions/13150449. - if (value <= std::numeric_limits::max()) + if (value <= std::numeric_limits::max()) { return static_cast(value); - else { + } else { constexpr auto TS_min = std::numeric_limits::min(); return TS_min + static_cast(value - TS_min); } } const uint8_t *data_ptr_; size_t remaining_bytes_; }; #endif // LLVM_FUZZER_FUZZED_DATA_PROVIDER_H_ diff --git a/src/test/fuzz/strprintf.cpp b/src/test/fuzz/strprintf.cpp new file mode 100644 index 000000000..7f400e0ab --- /dev/null +++ b/src/test/fuzz/strprintf.cpp @@ -0,0 +1,180 @@ +// Copyright (c) 2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +void test_one_input(const std::vector &buffer) { + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + const std::string format_string = + fuzzed_data_provider.ConsumeRandomLengthString(64); + + const int digits_in_format_specifier = + std::count_if(format_string.begin(), format_string.end(), IsDigit); + + // Avoid triggering the following crash bug: + // * strprintf("%987654321000000:", 1); + // + // Avoid triggering the following OOM bug: + // * strprintf("%.222222200000000$", 1.1); + // + // Upstream bug report: https://github.com/c42f/tinyformat/issues/70 + if (format_string.find("%") != std::string::npos && + digits_in_format_specifier >= 7) { + return; + } + + // Avoid triggering the following crash bug: + // * strprintf("%1$*1$*", -11111111); + // + // Upstream bug report: https://github.com/c42f/tinyformat/issues/70 + if (format_string.find("%") != std::string::npos && + format_string.find("$") != std::string::npos && + format_string.find("*") != std::string::npos && + digits_in_format_specifier > 0) { + return; + } + + // Avoid triggering the following crash bug: + // * strprintf("%.1s", (char*)nullptr); + // + // ()strprintf(format_string, (char*)nullptr); + // + // Upstream bug report: https://github.com/c42f/tinyformat/issues/70 + + try { + (void)strprintf(format_string, (signed char *)nullptr); + } catch (const tinyformat::format_error &) { + } + try { + (void)strprintf(format_string, (uint8_t *)nullptr); + } catch (const tinyformat::format_error &) { + } + try { + (void)strprintf(format_string, (void *)nullptr); + } catch (const tinyformat::format_error &) { + } + try { + (void)strprintf(format_string, (bool *)nullptr); + } catch (const tinyformat::format_error &) { + } + try { + (void)strprintf(format_string, (float *)nullptr); + } catch (const tinyformat::format_error &) { + } + try { + (void)strprintf(format_string, (double *)nullptr); + } catch (const tinyformat::format_error &) { + } + try { + (void)strprintf(format_string, (int16_t *)nullptr); + } catch (const tinyformat::format_error &) { + } + try { + (void)strprintf(format_string, (uint16_t *)nullptr); + } catch (const tinyformat::format_error &) { + } + try { + (void)strprintf(format_string, (int32_t *)nullptr); + } catch (const tinyformat::format_error &) { + } + try { + (void)strprintf(format_string, (uint32_t *)nullptr); + } catch (const tinyformat::format_error &) { + } + try { + (void)strprintf(format_string, (int64_t *)nullptr); + } catch (const tinyformat::format_error &) { + } + try { + (void)strprintf(format_string, (uint64_t *)nullptr); + } catch (const tinyformat::format_error &) { + } + + try { + switch (fuzzed_data_provider.ConsumeIntegralInRange(0, 13)) { + case 0: + (void)strprintf( + format_string, + fuzzed_data_provider.ConsumeRandomLengthString(32)); + break; + case 1: + (void)strprintf( + format_string, + fuzzed_data_provider.ConsumeRandomLengthString(32).c_str()); + break; + case 2: + (void)strprintf( + format_string, + fuzzed_data_provider.ConsumeIntegral()); + break; + case 3: + (void)strprintf( + format_string, + fuzzed_data_provider.ConsumeIntegral()); + break; + case 4: + (void)strprintf(format_string, + fuzzed_data_provider.ConsumeIntegral()); + break; + case 5: + (void)strprintf(format_string, + fuzzed_data_provider.ConsumeBool()); + break; + case 6: + (void)strprintf( + format_string, + fuzzed_data_provider.ConsumeFloatingPoint()); + break; + case 7: + (void)strprintf( + format_string, + fuzzed_data_provider.ConsumeFloatingPoint()); + break; + case 8: + (void)strprintf( + format_string, + fuzzed_data_provider.ConsumeIntegral()); + break; + case 9: + (void)strprintf( + format_string, + fuzzed_data_provider.ConsumeIntegral()); + break; + case 10: + (void)strprintf( + format_string, + fuzzed_data_provider.ConsumeIntegral()); + break; + case 11: + (void)strprintf( + format_string, + fuzzed_data_provider.ConsumeIntegral()); + break; + case 12: + (void)strprintf( + format_string, + fuzzed_data_provider.ConsumeIntegral()); + break; + case 13: + (void)strprintf( + format_string, + fuzzed_data_provider.ConsumeIntegral()); + break; + default: + assert(false); + } + } catch (const tinyformat::format_error &) { + } +} diff --git a/test/fuzz/test_runner.py b/test/fuzz/test_runner.py index c060a13aa..0c45b9053 100755 --- a/test/fuzz/test_runner.py +++ b/test/fuzz/test_runner.py @@ -1,141 +1,143 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Run fuzz test targets. """ import argparse import configparser import logging import os import subprocess import sys # Fuzzers known to lack a seed corpus in # https://github.com/Bitcoin-ABC/qa-assets/tree/master/fuzz_seed_corpus -FUZZERS_MISSING_CORPORA = [] +FUZZERS_MISSING_CORPORA = [ + "strprintf", +] def main(): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument( "-l", "--loglevel", dest="loglevel", default="INFO", help="log events at this level and higher to the console. Can be set to DEBUG, INFO, WARNING, ERROR or CRITICAL. Passing --loglevel DEBUG will output all logs to console.", ) parser.add_argument( '--export_coverage', action='store_true', help='If true, export coverage information to files in the seed corpus', ) parser.add_argument( 'seed_dir', help='The seed corpus to run on (must contain subfolders for each fuzz target).', ) parser.add_argument( 'target', nargs='*', help='The target(s) to run. Default is to run all targets.', ) args = parser.parse_args() # Set up logging logging.basicConfig( format='%(message)s', level=int(args.loglevel) if args.loglevel.isdigit( ) else args.loglevel.upper(), ) # Read config generated by configure. config = configparser.ConfigParser() configfile = os.path.abspath(os.path.dirname(__file__)) + "/../config.ini" config.read_file(open(configfile, encoding="utf8")) if not config["components"].getboolean("ENABLE_FUZZ"): logging.error("Must have fuzz targets built") sys.exit(1) test_dir = os.path.join( config["environment"]["BUILDDIR"], 'src', 'test', 'fuzz') # Build list of tests test_list_all = [ f for f in os.listdir(test_dir) if os.path.isfile(os.path.join(test_dir, f)) and os.access(os.path.join(test_dir, f), os.X_OK)] if not test_list_all: logging.error("No fuzz targets found") sys.exit(1) logging.info("Fuzz targets found: {}".format(test_list_all)) # By default run all args.target = args.target or test_list_all test_list_error = list(set(args.target).difference(set(test_list_all))) if test_list_error: logging.error( "Unknown fuzz targets selected: {}".format(test_list_error)) test_list_selection = list( set(test_list_all).intersection(set(args.target))) if not test_list_selection: logging.error("No fuzz targets selected") logging.info("Fuzz targets selected: {}".format(test_list_selection)) try: help_output = subprocess.run( args=[ os.path.join(test_dir, test_list_selection[0]), '-help=1', ], timeout=1, check=True, stderr=subprocess.PIPE, universal_newlines=True, ).stderr if "libFuzzer" not in help_output: logging.error("Must be built with libFuzzer") sys.exit(1) except subprocess.TimeoutExpired: logging.error( "subprocess timed out: Currently only libFuzzer is supported") sys.exit(1) run_once( corpus=args.seed_dir, test_list=test_list_selection, test_dir=test_dir, export_coverage=args.export_coverage, ) def run_once(*, corpus, test_list, test_dir, export_coverage): for t in test_list: corpus_path = os.path.join(corpus, t) if t in FUZZERS_MISSING_CORPORA: os.makedirs(corpus_path, exist_ok=True) args = [ os.path.join(test_dir, t), '-runs=1', '-detect_leaks=0', corpus_path, ] logging.debug('Run {} with args {}'.format(t, args)) output = subprocess.run( args, check=True, stderr=subprocess.PIPE, universal_newlines=True).stderr logging.debug('Output: {}'.format(output)) if not export_coverage: continue for line in output.splitlines(): if 'INITED' in line: with open(os.path.join(corpus, t + '_coverage'), 'w', encoding='utf-8') as cov_file: cov_file.write(line) break if __name__ == '__main__': main()