diff --git a/CMakeLists.txt b/CMakeLists.txt index c606f477..7bde6d6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ option( MANUAL_TZ_DB "User will set TZ DB manually by invoking set_install in th option( USE_TZ_DB_IN_DOT "Save the timezone database in the current folder" OFF ) option( BUILD_SHARED_LIBS "Build a shared version of library" OFF ) option( ENABLE_DATE_TESTING "Enable unit tests" OFF ) +option( ENABLE_FUZZ_TESTING "Build fuzz harnesses" OFF ) option( DISABLE_STRING_VIEW "Disable string view" OFF ) option( COMPILE_WITH_C_LOCALE "define ONLY_C_LOCALE=1" OFF ) option( BUILD_TZ_LIB "build/install of TZ library" OFF ) @@ -275,3 +276,10 @@ if( ENABLE_DATE_TESTING ) endif( ) endforeach( ) endif( ) + +#[===================================================================[ + fuzzing +#]===================================================================] +if(ENABLE_FUZZ_TESTING) + add_subdirectory(test/fuzzing) +endif() \ No newline at end of file diff --git a/test/fuzzing/CMakeLists.txt b/test/fuzzing/CMakeLists.txt new file mode 100644 index 00000000..05261718 --- /dev/null +++ b/test/fuzzing/CMakeLists.txt @@ -0,0 +1,72 @@ +if (NOT DEFINED ENV{OUT} OR NOT DEFINED ENV{LIB_FUZZING_ENGINE}) + message(FATAL_ERROR "Environment variables expected for OSSFuzz builds not provided! Cannot build fuzzer") +endif() + +# Create utility library +add_library(libfuzzutil + STATIC + fuzzutil.cpp +) + +target_include_directories(libfuzzutil + PUBLIC "inc" +) + +target_link_libraries(libfuzzutil + PUBLIC date::date +) + +target_compile_options(libfuzzutil + PRIVATE + $ENV{LIB_FUZZING_ENGINE} +) + +target_link_options(libfuzzutil + PRIVATE + $ENV{LIB_FUZZING_ENGINE} +) + +function(create_harness source_path) + + get_filename_component(harness_name "${source_path}" NAME_WE) + + add_executable(${harness_name} + "${source_path}" + ) + + target_link_libraries(${harness_name} + PRIVATE + libfuzzutil + ) + + target_compile_definitions(${harness_name} + PRIVATE + -DNDEBUG # Do not want assertions + ) + + target_compile_features(${harness_name} + PRIVATE + cxx_std_17 + ) + + target_compile_options(${harness_name} + PRIVATE + $ENV{LIB_FUZZING_ENGINE} + ) + + target_link_options(${harness_name} + PRIVATE + $ENV{LIB_FUZZING_ENGINE} + ) + + install(TARGETS ${harness_name} DESTINATION $ENV{OUT}) +endfunction() + +# Create a harness executable for all sources following fuzz_*.cpp pattern +file(GLOB_RECURSE + fuzz_source_files + fuzz_*.cpp +) +foreach(file ${fuzz_source_files}) + create_harness("${file}") +endforeach () \ No newline at end of file diff --git a/test/fuzzing/build.sh b/test/fuzzing/build.sh new file mode 100644 index 00000000..3c8f7d34 --- /dev/null +++ b/test/fuzzing/build.sh @@ -0,0 +1,20 @@ +#!/bin/bash -eu + +# Performs the necessary steps to build the fuzz harnesses utilized to prepare the test library for fuzz-test +# integration into OSSFuzz + +cd "$SRC"/date +mkdir -p build +cmake -S. -B build -DENABLE_FUZZ_TESTING=ON -DBUILD_TZ_LIB=OFF -DBUILD_SHARED_LIBS=OFF +cmake --build build --target install + +# Compress the corpus to the $OUT directory +zip -q $WORK/seed_corpus.zip test/fuzzing/corpus/* + +# Create a copy of the corpus in the $OUT directory for each target +for file in $(find "$OUT" -type f -regex ".*fuzz_.*") +do + target=$(basename -- "$file") + echo "Zipping corpus for target $target" + cp $WORK/seed_corpus.zip $OUT/"$target"_seed_corpus.zip +done \ No newline at end of file diff --git a/test/fuzzing/corpus/seed b/test/fuzzing/corpus/seed new file mode 100644 index 00000000..35cbee4d --- /dev/null +++ b/test/fuzzing/corpus/seed @@ -0,0 +1 @@ +%C %u %F %T \ No newline at end of file diff --git a/test/fuzzing/corpus/seed1 b/test/fuzzing/corpus/seed1 new file mode 100644 index 00000000..eefb0150 --- /dev/null +++ b/test/fuzzing/corpus/seed1 @@ -0,0 +1 @@ +Sun 2016-12-11 \ No newline at end of file diff --git a/test/fuzzing/corpus/seed2 b/test/fuzzing/corpus/seed2 new file mode 100644 index 00000000..ef983237 --- /dev/null +++ b/test/fuzzing/corpus/seed2 @@ -0,0 +1 @@ +Sunday 2016-12-11 \ No newline at end of file diff --git a/test/fuzzing/corpus/seed3 b/test/fuzzing/corpus/seed3 new file mode 100644 index 00000000..2973fdc6 --- /dev/null +++ b/test/fuzzing/corpus/seed3 @@ -0,0 +1 @@ +%A %F \ No newline at end of file diff --git a/test/fuzzing/corpus/seed4 b/test/fuzzing/corpus/seed4 new file mode 100644 index 00000000..7121dfec --- /dev/null +++ b/test/fuzzing/corpus/seed4 @@ -0,0 +1 @@ +%H %I %M %p %r %R %S %T %X %n %t %% \ No newline at end of file diff --git a/test/fuzzing/fuzz_date_parse.cpp b/test/fuzzing/fuzz_date_parse.cpp new file mode 100644 index 00000000..ec6a7ee6 --- /dev/null +++ b/test/fuzzing/fuzz_date_parse.cpp @@ -0,0 +1,18 @@ +#include + +#include "fuzzutil.hpp" + +using namespace date; +using namespace std::chrono; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, std::size_t size) +{ + FuzzedDataProvider fdp{data, size}; + const auto format = fdp.ConsumeRandomLengthString(); + auto date = consume_year_month_day(fdp); + std::istringstream in{fdp.ConsumeRandomLengthString()}; + + in >> parse(format.c_str(), date); + + return 0; +} \ No newline at end of file diff --git a/test/fuzzing/fuzz_format.cpp b/test/fuzzing/fuzz_format.cpp new file mode 100644 index 00000000..92c6f651 --- /dev/null +++ b/test/fuzzing/fuzz_format.cpp @@ -0,0 +1,23 @@ +#include + +#include "fuzzutil.hpp" + +using namespace date; +using namespace std::chrono; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, std::size_t size) +{ + FuzzedDataProvider fdp{data, size}; + const auto format_str = fdp.ConsumeRandomLengthString(); + std::ostringstream os; + + try { + os << format(format_str.c_str(), consume_year_month_day(fdp)); + } + catch (std::ios_base::failure&) + { + return -1; + } + + return 0; +} diff --git a/test/fuzzing/fuzz_from_stream.cpp b/test/fuzzing/fuzz_from_stream.cpp new file mode 100644 index 00000000..15c998a3 --- /dev/null +++ b/test/fuzzing/fuzz_from_stream.cpp @@ -0,0 +1,18 @@ +#include + +#include "fuzzutil.hpp" + +using namespace date; +using namespace std::chrono; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, std::size_t size) +{ + FuzzedDataProvider fdp{data, size}; + const auto format = fdp.ConsumeRandomLengthString(); + + std::istringstream is{fdp.ConsumeRandomLengthString()}; + year_month_day ymd; + from_stream(is, format.c_str(), ymd); + + return 0; +} \ No newline at end of file diff --git a/test/fuzzing/fuzz_islamic_conversion.cpp b/test/fuzzing/fuzz_islamic_conversion.cpp new file mode 100644 index 00000000..b4a5bf70 --- /dev/null +++ b/test/fuzzing/fuzz_islamic_conversion.cpp @@ -0,0 +1,37 @@ +#include + +#include "fuzzutil.hpp" +#include "date/islamic.h" +#include "date/iso_week.h" + +using namespace date; +using namespace std::chrono; + +enum class ConvType +{ + ToYMD, + ToIYMD, + ToISOWeek, + kMaxValue=ToISOWeek // NOLINT: FuzzedDataProvider requires this field as is +}; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, std::size_t size) +{ + FuzzedDataProvider fdp{data, size}; + std::ostringstream os; + switch (fdp.ConsumeEnum()) + { + case ConvType::ToYMD: + os << year_month_day{consume_islamic_year_month_day(fdp)}; + break; + case ConvType::ToIYMD: + os << islamic::year_month_day{consume_year_month_day(fdp)}; + break; + case ConvType::ToISOWeek: + os << iso_week::year_weeknum_weekday{consume_islamic_year_month_day(fdp)}; + break; + default: + return -1; + } + return 0; +} diff --git a/test/fuzzing/fuzz_julian_conversion.cpp b/test/fuzzing/fuzz_julian_conversion.cpp new file mode 100644 index 00000000..e32f0fc3 --- /dev/null +++ b/test/fuzzing/fuzz_julian_conversion.cpp @@ -0,0 +1,37 @@ +#include + +#include "fuzzutil.hpp" +#include "date/julian.h" +#include "date/iso_week.h" + +using namespace date; +using namespace std::chrono; + +enum class ConvType +{ + ToYMD, + ToJYMD, + ToISOWeek, + kMaxValue=ToISOWeek // NOLINT: FuzzedDataProvider requires this field as is +}; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, std::size_t size) +{ + FuzzedDataProvider fdp{data, size}; + std::ostringstream os; + switch (fdp.ConsumeEnum()) + { + case ConvType::ToYMD: + os << year_month_day{consume_julian_year_month_day(fdp)}; + break; + case ConvType::ToJYMD: + os << julian::year_month_day{consume_year_month_day(fdp)}; + break; + case ConvType::ToISOWeek: + os << iso_week::year_weeknum_weekday{consume_julian_year_month_day(fdp)}; + break; + default: + return -1; + } + return 0; +} \ No newline at end of file diff --git a/test/fuzzing/fuzz_make_time.cpp b/test/fuzzing/fuzz_make_time.cpp new file mode 100644 index 00000000..7cc0a0b3 --- /dev/null +++ b/test/fuzzing/fuzz_make_time.cpp @@ -0,0 +1,51 @@ +#include + +#include "fuzzutil.hpp" + +using namespace date; +using namespace std::chrono; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, std::size_t size) +{ + FuzzedDataProvider fdp{data, size}; + + const auto dur_val = fdp.ConsumeIntegral(); + + switch (fdp.ConsumeEnum()) + { + case duration_type::NANOSECONDS: + make_time(nanoseconds{dur_val}); + break; + case duration_type::MICROSECONDS: + make_time(microseconds{dur_val}); + break; + case duration_type::MILLISECONDS: + make_time(milliseconds {dur_val}); + break; + case duration_type::SECONDS: + make_time(seconds {dur_val}); + break; + case duration_type::MINUTES: + make_time(minutes {dur_val}); + break; + case duration_type::HOURS: + make_time(hours {dur_val}); + break; + case duration_type::DAYS: + make_time(days {dur_val}); + break; + case duration_type::WEEKS: + make_time(weeks {dur_val}); + break; + case duration_type::MONTHS: + make_time(months {dur_val}); + break; + case duration_type::YEARS: + make_time(years {dur_val}); + break; + default: + return -1; + } + + return 0; +} \ No newline at end of file diff --git a/test/fuzzing/fuzz_sys_parse.cpp b/test/fuzzing/fuzz_sys_parse.cpp new file mode 100644 index 00000000..4406334f --- /dev/null +++ b/test/fuzzing/fuzz_sys_parse.cpp @@ -0,0 +1,25 @@ +#include + +#include "fuzzutil.hpp" + +using namespace date; +using namespace std::chrono; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, std::size_t size) +{ + FuzzedDataProvider fdp{data, size}; + const auto format_str = fdp.ConsumeRandomLengthString(); + std::istringstream in{fdp.ConsumeRandomLengthString()}; + + if (fdp.ConsumeBool()) + { + sys_days tp; + in >> parse(format_str, tp); + } + else + { + sys_seconds tp; + in >> parse(format_str, tp); + } + return 0; +} \ No newline at end of file diff --git a/test/fuzzing/fuzz_to_stream.cpp b/test/fuzzing/fuzz_to_stream.cpp new file mode 100644 index 00000000..639b4177 --- /dev/null +++ b/test/fuzzing/fuzz_to_stream.cpp @@ -0,0 +1,17 @@ +#include + +#include "fuzzutil.hpp" + +using namespace date; +using namespace std::chrono; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, std::size_t size) +{ + FuzzedDataProvider fdp{data, size}; + const auto format = fdp.ConsumeRandomLengthString(); + + std::ostringstream os; + to_stream(os, format.c_str(), consume_year_month_day(fdp)); + + return 0; +} \ No newline at end of file diff --git a/test/fuzzing/fuzz_week_to_stream.cpp b/test/fuzzing/fuzz_week_to_stream.cpp new file mode 100644 index 00000000..45500edb --- /dev/null +++ b/test/fuzzing/fuzz_week_to_stream.cpp @@ -0,0 +1,16 @@ +#include + +#include "fuzzutil.hpp" +#include "date/iso_week.h" + +using namespace date; +using namespace std::chrono; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, std::size_t size) +{ + FuzzedDataProvider fdp{data, size}; + std::ostringstream os; + auto a = iso_week::year_weeknum_weekday{sys_days{days{fdp.ConsumeIntegral()}}}; + os << a; + return 0; +} \ No newline at end of file diff --git a/test/fuzzing/fuzzutil.cpp b/test/fuzzing/fuzzutil.cpp new file mode 100644 index 00000000..f9ba61ad --- /dev/null +++ b/test/fuzzing/fuzzutil.cpp @@ -0,0 +1,33 @@ +#include "fuzzutil.hpp" + +date::year_month_day consume_year_month_day(FuzzedDataProvider &fdp) +{ + auto month = fdp.ConsumeIntegral(); + auto day = fdp.ConsumeIntegral(); + return date::year{fdp.ConsumeIntegral()}/month/day; +} + +date::day consume_day(FuzzedDataProvider &fdp) { + return date::day{fdp.ConsumeIntegral()}; +} + +date::month_day consume_month_day(FuzzedDataProvider &fdp) { + return {date::month{fdp.ConsumeIntegral()}, + date::day{fdp.ConsumeIntegral()}}; +} + +julian::year_month_day consume_julian_year_month_day(FuzzedDataProvider &fdp) { + return { + julian::year{fdp.ConsumeIntegral()}, + julian::month{fdp.ConsumeIntegral()}, + julian::day{fdp.ConsumeIntegral()} + }; +} + +islamic::year_month_day consume_islamic_year_month_day(FuzzedDataProvider &fdp) { + return { + islamic::year{fdp.ConsumeIntegral()}, + islamic::month{fdp.ConsumeIntegral()}, + islamic::day{fdp.ConsumeIntegral()} + }; +} diff --git a/test/fuzzing/inc/fuzzutil.hpp b/test/fuzzing/inc/fuzzutil.hpp new file mode 100644 index 00000000..47f11b34 --- /dev/null +++ b/test/fuzzing/inc/fuzzutil.hpp @@ -0,0 +1,38 @@ +#ifndef DATE_FUZZUTIL_HPP +#define DATE_FUZZUTIL_HPP + +#include + +#include "date/date.h" +#include "date/julian.h" +#include "date/islamic.h" + +enum class duration_type +{ + NANOSECONDS, + MICROSECONDS, + MILLISECONDS, + SECONDS, + MINUTES, + HOURS, + DAYS, + WEEKS, + MONTHS, + YEARS, + kMaxValue=YEARS // NOLINT: FuzzedDataProvider requires kMaxValue +}; + +[[nodiscard]] date::year_month_day consume_year_month_day(FuzzedDataProvider &fdp); + +[[nodiscard]] julian::year_month_day consume_julian_year_month_day(FuzzedDataProvider &fdp); + +[[nodiscard]] islamic::year_month_day consume_islamic_year_month_day(FuzzedDataProvider &fdp); + +[[nodiscard]] date::day consume_day(FuzzedDataProvider &fdp); + +[[nodiscard]] std::chrono::duration consume_duration(FuzzedDataProvider &fdp); + +[[nodiscard]] date::month_day consume_month_day(FuzzedDataProvider &fdp); + + +#endif //DATE_FUZZUTIL_HPP