From: Jason Crowder Date: Sat, 18 Apr 2026 15:24:24 +0000 (-0500) Subject: decompress: Add initial decompression fuzzers and build support. (#5232) X-Git-Tag: 3.12.2.0~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=48a3d2ed204d98d7345eb6a3401f073c47b2dfb6;p=thirdparty%2Fsnort3.git decompress: Add initial decompression fuzzers and build support. (#5232) --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 438fa8a78..76c56636e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,10 @@ if (ENABLE_UNIT_TESTS OR ENABLE_BENCHMARK_TESTS) add_custom_target (check COMMAND ${CMAKE_CTEST_COMMAND}) endif (ENABLE_UNIT_TESTS OR ENABLE_BENCHMARK_TESTS) +if (ENABLE_FUZZERS) + add_custom_target (fuzz) +endif (ENABLE_FUZZERS) + add_subdirectory (src) add_subdirectory (tools) add_subdirectory (lua) diff --git a/README.md b/README.md index 5d4940664..339e0d91a 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ topics: * [Download](#download) * [Build Snort](#build-snort) * [Run Snort](#run-snort) +* [Fuzz Testing](#fuzz-testing) * [Documentation](#documentation) * [Squeal](#squeal) @@ -175,6 +176,77 @@ snort_defaults.lua. Additional examples are given in doc/usage.txt. +# FUZZ TESTING + +Snort includes libFuzzer fuzz targets for testing various components. Building +and running fuzzers requires Clang. + +* Build fuzzers with AddressSanitizer: + + ```shell + CC=clang CXX=clang++ ./configure_cmake.sh --enable-fuzz-sanitizer --enable-fuzzers \ + --enable-address-sanitizer + cd build + make fuzz -j $(nproc) + ``` + +* If you need to specify DAQ include and library paths: + + ```shell + CC=clang CXX=clang++ ./configure_cmake.sh --enable-fuzz-sanitizer --enable-fuzzers \ + --enable-address-sanitizer --with-daq-includes=/path/to/daq/include --with-daq-libraries=/path/to/daq/lib + cd build + make fuzz -j $(nproc) + ``` + +* Run a fuzz target by providing a corpus directory. Each target is built + under `build/fuzz/`: + + ```shell + mkdir file_olefile_fuzz_corpus + ./fuzz/file_olefile_fuzz ./file_olefile_fuzz_corpus + ``` + +* If DAQ libraries are not on the default library path, set `LD_LIBRARY_PATH`: + + ```shell + LD_LIBRARY_PATH=/path/to/daq/lib ./fuzz/file_olefile_fuzz ./file_olefile_fuzz_corpus + ``` + +* Press Ctrl+C to stop the fuzzer. List saved corpus files with: + + ```shell + ls ./file_olefile_fuzz_corpus + ``` + +* Build with coverage instrumentation instead of AddressSanitizer to measure + which code paths the fuzzer exercises: + + ```shell + CC=clang CXX=clang++ ./configure_cmake.sh --enable-fuzz-sanitizer --enable-fuzzers \ + --enable-fuzz-coverage + cd build + make fuzz -j $(nproc) + ``` + +* Run the coverage build against an existing corpus (using `-runs=0` to replay + without generating new inputs): + + ```shell + LLVM_PROFILE_FILE="file_olefile_fuzz.profraw" ./fuzz/file_olefile_fuzz \ + file_olefile_fuzz_corpus -runs=0 -detect_leaks=0 + ``` + +* Generate an HTML coverage report: + + ```shell + llvm-profdata merge -sparse file_olefile_fuzz.profraw -o file_olefile_fuzz.profdata + llvm-cov show -format=html -instr-profile=file_olefile_fuzz.profdata \ + ./fuzz/file_olefile_fuzz -output-dir=file_olefile_fuzz_cov_html + ``` + + Open `file_olefile_fuzz_cov_html/index.html` in a browser to view the report. + # DOCUMENTATION Take a look at the manual, parts of which are generated by the code so it diff --git a/cmake/configure_options.cmake b/cmake/configure_options.cmake index 8b556cd08..5732d12e8 100644 --- a/cmake/configure_options.cmake +++ b/cmake/configure_options.cmake @@ -165,6 +165,49 @@ if ( ENABLE_UB_SANITIZER ) endif () endif ( ENABLE_UB_SANITIZER ) +if ( ENABLE_FUZZ_SANITIZER ) + set ( FUZZ_CXX_FLAGS "-fsanitize=fuzzer" ) + set ( FUZZ_LINKER_FLAGS "-fsanitize=fuzzer" ) + + set(FUZZ_TEST_SOURCE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/fuzz_test.cc") + file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp") + file(WRITE "${FUZZ_TEST_SOURCE}" + "extern \"C\" int LLVMFuzzerTestOneInput(const char *data, int size) { return 0; }\n") + + try_compile(HAVE_FUZZ_SANITIZER + ${CMAKE_BINARY_DIR} + ${FUZZ_TEST_SOURCE} + COMPILE_DEFINITIONS "${FUZZ_CXX_FLAGS}" + LINK_OPTIONS "${FUZZ_LINKER_FLAGS}" + OUTPUT_VARIABLE FUZZ_TEST_OUTPUT + ) + + if ( HAVE_FUZZ_SANITIZER ) + set ( FUZZER_CXX_FLAGS "${FUZZ_CXX_FLAGS}" ) + set ( FUZZER_LINKER_FLAGS "${FUZZ_LINKER_FLAGS}" ) + message(STATUS "Fuzzer sanitizer enabled") + else () + message ( SEND_ERROR "Could not enable the fuzz sanitizer" ) + endif (HAVE_FUZZ_SANITIZER) +endif ( ENABLE_FUZZ_SANITIZER ) + +if ( ENABLE_FUZZERS AND NOT LIB_FUZZING_ENGINE AND NOT ENABLE_FUZZ_SANITIZER ) + message ( FATAL_ERROR "ENABLE_FUZZERS requires either LIB_FUZZING_ENGINE or ENABLE_FUZZ_SANITIZER to be set" ) +endif () + +if ( ENABLE_FUZZ_COVERAGE ) + set ( FUZZ_COVERAGE_CXX_FLAGS "-fprofile-instr-generate -fcoverage-mapping" ) + set ( CMAKE_REQUIRED_FLAGS "${FUZZ_COVERAGE_CXX_FLAGS}" ) + check_cxx_compiler_flag ( "${FUZZ_COVERAGE_CXX_FLAGS}" HAVE_FUZZ_COVERAGE ) + unset ( CMAKE_REQUIRED_FLAGS ) + if ( HAVE_FUZZ_COVERAGE ) + set ( COVERAGE_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} ${FUZZ_COVERAGE_CXX_FLAGS}" ) + set ( EXTRA_LINKER_FLAGS "${EXTRA_LINKER_FLAGS} ${FUZZ_COVERAGE_CXX_FLAGS}" ) + else () + message ( SEND_ERROR "Could not enable -fprofile-instr-generate and -fcoverage-mapping" ) + endif (HAVE_FUZZ_COVERAGE) +endif ( ENABLE_FUZZ_COVERAGE ) + if ( ENABLE_TCMALLOC ) if ( ENABLE_ADDRESS_SANITIZER ) message ( SEND_ERROR "TCMalloc cannot be used at the same time as address sanitizer!" ) diff --git a/cmake/create_options.cmake b/cmake/create_options.cmake index 2d081ac16..2564e72ee 100644 --- a/cmake/create_options.cmake +++ b/cmake/create_options.cmake @@ -26,6 +26,10 @@ option ( ENABLE_LARGE_PCAP "Enable support for pcaps larger than 2 GB" OFF ) option ( ENABLE_STDLOG "Use file descriptor 3 instead of stdout for alerts" OFF ) option ( ENABLE_TSC_CLOCK "Use timestamp counter register clock (x86 and arm only)" OFF ) +option ( ENABLE_FUZZERS "enable fuzzers" OFF ) +option ( ENABLE_FUZZ_SANITIZER "Enable libFuzzer fuzzer sanitizer builds" OFF ) +set ( LIB_FUZZING_ENGINE "" CACHE FILEPATH "Path to external fuzzing engine library (e.g. FuzzingEngine.a)" ) + # documentation option ( MAKE_HTML_DOC "Create the HTML documentation" ON ) option ( MAKE_PDF_DOC "Create the PDF documentation" ON ) @@ -52,6 +56,7 @@ option ( ENABLE_UB_SANITIZER "enable undefined behavior sanitizer support" OFF ) option ( ENABLE_TCMALLOC "enable using tcmalloc for dynamic memory management" OFF ) option ( ENABLE_JEMALLOC "enable using jemalloc for dynamic memory management" OFF ) option ( ENABLE_CODE_COVERAGE "Whether to enable code coverage support" OFF ) +option ( ENABLE_FUZZ_COVERAGE "Enable settings for collecting fuzzer code coverage" OFF ) # signals set ( diff --git a/cmake/macros.cmake b/cmake/macros.cmake index 9333e0169..fbe5d46ae 100644 --- a/cmake/macros.cmake +++ b/cmake/macros.cmake @@ -69,6 +69,27 @@ function (add_cpputest testname) endfunction (add_cpputest) +function (add_fuzzer name) + if ( ENABLE_FUZZERS ) + set(multiValueArgs SOURCES LIBS) + cmake_parse_arguments(Fuzzer "" "" "${multiValueArgs}" ${ARGN}) + add_executable(${name} EXCLUDE_FROM_ALL ${name}.cc ${Fuzzer_SOURCES}) + target_link_libraries(${name} PRIVATE ${Fuzzer_LIBS} ${EXTERNAL_LIBRARIES}) + + if ( LIB_FUZZING_ENGINE ) + target_link_libraries(${name} PRIVATE ${LIB_FUZZING_ENGINE}) + elseif ( FUZZER_CXX_FLAGS ) + target_compile_options(${name} PRIVATE ${FUZZER_CXX_FLAGS}) + target_link_libraries(${name} PRIVATE ${FUZZER_LINKER_FLAGS}) + endif() + + set_property(TARGET ${name} PROPERTY RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/fuzz") + + add_dependencies(fuzz ${name}) + endif( ENABLE_FUZZERS ) +endfunction (add_fuzzer) + + function (add_catch_test testname) if ( ENABLE_UNIT_TESTS OR ENABLE_BENCHMARK_TESTS ) set(options NO_TEST_SOURCE) diff --git a/configure_cmake.sh b/configure_cmake.sh index a89e1ad4e..6a9c8eeb3 100755 --- a/configure_cmake.sh +++ b/configure_cmake.sh @@ -75,6 +75,13 @@ Optional Features: --enable-appid-third-party enable third party appid --enable-unit-tests build unit tests + --enable-fuzzers build fuzzers + --disable-fuzzers do not build fuzzers + --enable-fuzz-sanitizer build with libFuzzer sanitizer + --disable-fuzz-sanitizer + do not build with libFuzzer sanitizer + --enable-fuzz-coverage enable settings for collecting fuzzing code coverage + --disable-fuzz-coverage disable settings for collecting fuzzing code coverage --enable-ccache enable ccache support --disable-static-daq link static DAQ modules --disable-html-docs don't create the HTML documentation @@ -132,6 +139,8 @@ Optional Packages: libnuma include directory --with-libnuma-libraries=DIR libnuma library directory + --with-fuzzing-engine=FILE + external fuzzing engine library (e.g. FuzzingEngine.a) Some influential variable definitions: SIGNAL_SNORT_RELOAD= @@ -372,6 +381,27 @@ while [ $# -ne 0 ]; do --disable-unit-tests) append_cache_entry ENABLE_UNIT_TESTS BOOL false ;; + --enable-fuzzers) + append_cache_entry ENABLE_FUZZERS BOOL true + ;; + --disable-fuzzers) + append_cache_entry ENABLE_FUZZERS BOOL false + ;; + --with-fuzzing-engine=*) + append_cache_entry LIB_FUZZING_ENGINE FILEPATH $optarg + ;; + --enable-fuzz-sanitizer) + append_cache_entry ENABLE_FUZZ_SANITIZER BOOL true + ;; + --disable-fuzz-sanitizer) + append_cache_entry ENABLE_FUZZ_SANITIZER BOOL false + ;; + --enable-fuzz-coverage) + append_cache_entry ENABLE_FUZZ_COVERAGE BOOL true + ;; + --disable-fuzz-coverage) + append_cache_entry ENABLE_FUZZ_COVERAGE BOOL false + ;; --enable-benchmark-tests) append_cache_entry ENABLE_BENCHMARK_TESTS BOOL true ;; diff --git a/src/decompress/CMakeLists.txt b/src/decompress/CMakeLists.txt index e42179c9a..3c4a394a9 100644 --- a/src/decompress/CMakeLists.txt +++ b/src/decompress/CMakeLists.txt @@ -1,4 +1,8 @@ +if(ENABLE_FUZZERS) + add_subdirectory (fuzz) +endif(ENABLE_FUZZERS) + add_subdirectory (test) set( DECOMPRESS_INCLUDES diff --git a/src/decompress/fuzz/CMakeLists.txt b/src/decompress/fuzz/CMakeLists.txt new file mode 100644 index 000000000..6fe933c21 --- /dev/null +++ b/src/decompress/fuzz/CMakeLists.txt @@ -0,0 +1,15 @@ +add_fuzzer( file_decomp_zip_fuzz + SOURCES ../file_decomp_zip.cc + ../file_decomp_pdf.cc + ../file_decomp_swf.cc + ../file_decomp.cc + ../../helpers/boyer_moore_search.cc +) + +add_fuzzer( file_olefile_fuzz + SOURCES ../file_olefile.cc + ../file_oleheader.cc + ../../helpers/boyer_moore_search.cc + ../../helpers/utf.cc +) + diff --git a/src/decompress/fuzz/file_decomp_zip_fuzz.cc b/src/decompress/fuzz/file_decomp_zip_fuzz.cc new file mode 100644 index 000000000..0daff9b07 --- /dev/null +++ b/src/decompress/fuzz/file_decomp_zip_fuzz.cc @@ -0,0 +1,57 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2026-2026 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// file_decomp_zip_fuzz.cc author Jason Crowder + +#include "../file_decomp_zip.h" + +using namespace snort; + +// Matches DEFAULT_DECOMP from mime/file_mime_config.h +// Duplicated here to avoid pulling in dependencies +#define DEFAULT_DECOMP 100000 + +uint8_t out_data[DEFAULT_DECOMP] = { }; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + uint32_t clamped_size = (uint32_t)size; + + if (size > UINT32_MAX) + { + return 0; + } + + fd_session_t* fd = File_Decomp_New(); + + fd->File_Type = FILE_TYPE_ZIP; + fd->Next_In = data; + fd->Avail_In = clamped_size; + fd->Next_Out = out_data; + fd->Avail_Out = sizeof(out_data); + + File_Decomp_Init_ZIP(fd); + + File_Decomp_ZIP(fd); + + File_Decomp_End_ZIP(fd); + + File_Decomp_Free(fd); + + return 0; +} diff --git a/src/decompress/fuzz/file_olefile_fuzz.cc b/src/decompress/fuzz/file_olefile_fuzz.cc new file mode 100644 index 000000000..430e13ee2 --- /dev/null +++ b/src/decompress/fuzz/file_olefile_fuzz.cc @@ -0,0 +1,64 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2026-2026 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// file_olefile_fuzz.cc author Jason Crowder + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "helpers/boyer_moore_search.h" +#include "../file_olefile.h" + +using namespace snort; + +THREAD_LOCAL const snort::Trace* vba_data_trace = nullptr; +Packet* DetectionEngine::get_current_packet() { return nullptr; } +uint8_t TraceApi::get_constraints_generation() { return 0; } +void TraceApi::filter(snort::Packet const&) { } +LiteralSearch::Handle* search_handle = nullptr; +const LiteralSearch* searcher = nullptr; +static snort::BoyerMooreSearchNoCase static_searcher((const uint8_t*)"ATTRIBUT", 8); +namespace snort +{ +void trace_vprintf(const char* name, TraceLevel log_level, + const char* trace_option, const Packet* p, const char* fmt, va_list ap) { } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + uint8_t* vba_buf = nullptr; + uint32_t vba_buf_len = 0; + uint32_t clamped_size = (uint32_t)size; + + if (size > UINT32_MAX) + { + return 0; + } + + searcher = &static_searcher; + + oleprocess(data, clamped_size, vba_buf, vba_buf_len); + + if (vba_buf && vba_buf_len) + { + delete[] vba_buf; + } + + return 0; +}