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)
* [Download](#download)
* [Build Snort](#build-snort)
* [Run Snort](#run-snort)
+* [Fuzz Testing](#fuzz-testing)
* [Documentation](#documentation)
* [Squeal](#squeal)
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
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!" )
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 )
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 (
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)
--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
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>
--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
;;
+if(ENABLE_FUZZERS)
+ add_subdirectory (fuzz)
+endif(ENABLE_FUZZERS)
+
add_subdirectory (test)
set( DECOMPRESS_INCLUDES
--- /dev/null
+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
+)
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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;
+}
--- /dev/null
+//--------------------------------------------------------------------------
+// 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;
+}