]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
decompress: Add initial decompression fuzzers and build support. (#5232)
authorJason Crowder <jasocrow@cisco.com>
Sat, 18 Apr 2026 15:24:24 +0000 (10:24 -0500)
committerGitHub <noreply@github.com>
Sat, 18 Apr 2026 15:24:24 +0000 (11:24 -0400)
CMakeLists.txt
README.md
cmake/configure_options.cmake
cmake/create_options.cmake
cmake/macros.cmake
configure_cmake.sh
src/decompress/CMakeLists.txt
src/decompress/fuzz/CMakeLists.txt [new file with mode: 0644]
src/decompress/fuzz/file_decomp_zip_fuzz.cc [new file with mode: 0644]
src/decompress/fuzz/file_olefile_fuzz.cc [new file with mode: 0644]

index 438fa8a78451e218f5e90356b43d2181698b33c9..76c56636ebec5efdf535ecb54b8e2a908260e197 100644 (file)
@@ -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)
index 5d49406642372824a67caf8b5957258cfccfcd2f..339e0d91a748355edf45175029cba79ae78af57f 100644 (file)
--- 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
index 8b556cd08c234694fe249cf8e40cc41af06aa5e4..5732d12e8874d4e5de8726bfdbd60fb7c428c9bb 100644 (file)
@@ -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!" )
index 2d081ac164a98af80932ee0f2c959202c817bd77..2564e72eefd622f3396a5890d8b750697b4b01e8 100644 (file)
@@ -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 (
index 9333e0169365b3eea4a1b3d0e22bffdf1931743b..fbe5d46ae98eeef69a58575125a4c2c594712e30 100644 (file)
@@ -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)
index a89e1ad4e25105a60232785d5995a2f3eae870b1..6a9c8eeb31af16d0eed4027c1b93a5beb7aa065c 100755 (executable)
@@ -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=<int>
@@ -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
             ;;
index e42179c9a582b43deebd69da016e308eda095557..3c4a394a9b6f5b2717d9b8e65f767c88bd4f813a 100644 (file)
@@ -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 (file)
index 0000000..6fe933c
--- /dev/null
@@ -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 (file)
index 0000000..0daff9b
--- /dev/null
@@ -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 <jasocrow@cisco.com>
+
+#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 (file)
index 0000000..430e13e
--- /dev/null
@@ -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 <jasocrow@cisco.com>
+
+#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;
+}