diff --git a/CMakeLists.txt b/CMakeLists.txt index 8329d7ec..0e855c77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,10 @@ # Build example files (default off). To build the examples, # pass `-DBUILD_EXAMPLES=ON` during configuration. # +# BUILD_FUZZERS +# Build fuzz harnesses (default off). To build the harnesses, +# pass `-DBUILD_FUZZERS=ON` during configuration. +# # USE_STANDARD_TMPFILE # Use the standard tmpfile() function (default off). To enable # the standard tmpfile, pass `-DUSE_STANDARD_TMPFILE=ON` @@ -127,6 +131,7 @@ SET(ZLIB_ROOT "" CACHE STRING "Optional root for the ZLIB installation") option(BUILD_TESTS "Build libxlsxwriter tests" OFF) option(BUILD_EXAMPLES "Build libxlsxwriter examples" OFF) +option(BUILD_FUZZERS "Build harness(es) for fuzzing" OFF) option(USE_SYSTEM_MINIZIP "Use system minizip installation" OFF) option(USE_STANDARD_TMPFILE "Use the C standard library's tmpfile()" OFF) option(USE_NO_MD5 "Build libxlsxwriter without third party MD5 lib" OFF) @@ -283,6 +288,7 @@ endif() set(LXW_PROJECT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") set(LXW_LIB_DIR "${LXW_PROJECT_DIR}/lib") add_library(${PROJECT_NAME} "") + set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION ${SOVERSION}) target_sources(${PROJECT_NAME} PRIVATE ${LXW_SOURCES} @@ -421,6 +427,12 @@ if(BUILD_EXAMPLES) endforeach(source) endif() +# FUZZING +# ------- +if (BUILD_FUZZERS AND DEFINED ENV{LIB_FUZZING_ENGINE}) + add_subdirectory(dev/fuzzing) +endif() + # INSTALL # ------- include(GNUInstallDirs) diff --git a/dev/fuzzing/CMakeLists.txt b/dev/fuzzing/CMakeLists.txt new file mode 100644 index 00000000..36fb32af --- /dev/null +++ b/dev/fuzzing/CMakeLists.txt @@ -0,0 +1,23 @@ +# Utilized by OSSFuzz to build the harness(es) for continuous fuzz-testing +# OSSFuzz defines the following environment variables, that this target relies upon: +# CXX, CFLAGS, LIB_FUZZING_ENGINE, OUT + +add_definitions(-DNDEBUG) # Do not want assertions + +if (DEFINED ENV{CFLAGS}) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} $ENV{CFLAGS}") +endif() +if (DEFINED ENV{CXXFLAGS}) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} $ENV{CXXFLAGS}") +endif() + + +add_executable(xlsx_fuzzer xlsx_fuzzer.cpp) +target_link_libraries(xlsx_fuzzer PRIVATE ${PROJECT_NAME} $ENV{LIB_FUZZING_ENGINE}) +target_compile_features(xlsx_fuzzer PRIVATE cxx_std_17) + +if (DEFINED ENV{OUT}) + install(TARGETS xlsx_fuzzer DESTINATION $ENV{OUT}) +else () + message(WARNING "Cannot install if $OUT is not defined!") +endif () \ No newline at end of file diff --git a/dev/fuzzing/build.sh b/dev/fuzzing/build.sh new file mode 100755 index 00000000..f3c93ecd --- /dev/null +++ b/dev/fuzzing/build.sh @@ -0,0 +1,11 @@ +cd $SRC/libxlsxwriter + +printenv + +mkdir -p build +cmake -S . -B build -DBUILD_FUZZERS=ON && cmake --build build --target install + +# Build the corpus using the existing xlsx files in the source +mkdir -p corpus +find $SRC/libxlsxwriter -name "*.xlsx" -exec cp {} corpus \; +zip -q $OUT/xlsx_fuzzer_seed_corpus.zip corpus/* diff --git a/dev/fuzzing/xlsx_fuzzer.cpp b/dev/fuzzing/xlsx_fuzzer.cpp new file mode 100644 index 00000000..b4fd0182 --- /dev/null +++ b/dev/fuzzing/xlsx_fuzzer.cpp @@ -0,0 +1,84 @@ +#include +#include +#include + +#include "xlsxwriter.h" + +const std::string mem_dir{"/dev/shm"}; +const std::string file_template = "/fuzzXXXXXX"; +char temp_file_dir[FILENAME_MAX] = {0}; + +/** + * \brief: Performs all prep-work needed for continuous fuzzing + * \return: Whether initialization was successful + */ +int init_for_fuzzing() +{ + // Initialize the temporary file directory, based off what is available on the system + + if (0 == access(mem_dir.c_str(), W_OK | R_OK)) + { + // We can read and write to the in-memory directory + memcpy(temp_file_dir, mem_dir.c_str(), strnlen(mem_dir.c_str(), FILENAME_MAX)); + } + else + { + // Default to a temporary directory + const char* tmp_prefix = getenv("TMPDIR"); + if (nullptr == tmp_prefix) + { + tmp_prefix = "/tmp"; + } + memcpy((void*) temp_file_dir, tmp_prefix, strnlen(tmp_prefix, FILENAME_MAX)); + } + return 0; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, const size_t size) +{ + static bool init_fuzzing = init_for_fuzzing(); + + char fuzz_file[FILENAME_MAX + 1] = {0}; + int fuzz_fd = 0; + int ret = -1; + ssize_t wc = 0; + size_t byte_len; + lxw_workbook *workbook = nullptr; + lxw_worksheet *worksheet = nullptr; + FuzzedDataProvider fdp{data, size}; + std::vector file_bytes{}; + + strncpy(fuzz_file, temp_file_dir, strlen(temp_file_dir)); + strncat(fuzz_file, file_template.c_str(), file_template.length()); + + if ((fuzz_fd = mkstemp(fuzz_file)) < 0) + { + goto fail; + } + + byte_len = fdp.ConsumeIntegralInRange(0, fdp.remaining_bytes()); + file_bytes = fdp.ConsumeBytes(byte_len); + write(fuzz_fd, file_bytes.data(), std::min(file_bytes.size(), byte_len)); + + workbook = workbook_new(fuzz_file); + worksheet = workbook_add_worksheet(workbook, nullptr); + + for (int row = 0; row < fdp.ConsumeIntegralInRange(0, 25); ++row) + { + for (int col = 0; col < fdp.ConsumeIntegralInRange(0, 25); ++col) + { + worksheet_write_string(worksheet, row, col, fdp.ConsumeRandomLengthString().c_str(), nullptr); + } + } + + ret = 0; + + fail: + if (nullptr != workbook) + { + workbook_close(workbook); + } + close(fuzz_fd); + unlink(fuzz_file); + return ret; +}