From 8f1b34b915a61239fe8205170665948cd6c507b4 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Tue, 21 Apr 2020 20:16:00 -0700 Subject: [PATCH 01/17] Add basic structure for fuzzing targets General changes: * Add -D/--deterministic command line option as convenience/alternative to -G/--load-seeds (i.e. no file needed, it just uses zero-initialized random seeds). It also changes Broker data stores over to using deterministic timing rather than real time. * Add option to make Reporter abort on runtime scripting errors --- configure | 4 + src/CMakeLists.txt | 21 +- src/Options.cc | 9 +- src/Options.h | 2 + src/Reporter.cc | 11 +- src/Reporter.h | 3 +- src/Sessions.cc | 17 + src/Sessions.h | 3 + src/broker/Manager.cc | 14 +- src/broker/Manager.h | 9 +- src/fuzzers/CMakeLists.txt | 30 ++ src/fuzzers/FuzzBuffer.cc | 56 +++ src/fuzzers/FuzzBuffer.h | 24 + src/fuzzers/fuzz-setup.h | 41 ++ src/fuzzers/pop3.cc | 83 ++++ src/main.cc | 919 +----------------------------------- src/util.cc | 5 +- src/util.h | 14 +- src/zeek-setup.cc | 922 +++++++++++++++++++++++++++++++++++++ src/zeek-setup.h | 31 ++ 20 files changed, 1290 insertions(+), 928 deletions(-) create mode 100644 src/fuzzers/CMakeLists.txt create mode 100644 src/fuzzers/FuzzBuffer.cc create mode 100644 src/fuzzers/FuzzBuffer.h create mode 100644 src/fuzzers/fuzz-setup.h create mode 100644 src/fuzzers/pop3.cc create mode 100644 src/zeek-setup.cc create mode 100644 src/zeek-setup.h diff --git a/configure b/configure index 48c35e64a5..a54e1248d2 100755 --- a/configure +++ b/configure @@ -47,6 +47,7 @@ Usage: $0 [OPTION]... [VAR=VALUE]... Optional Features: --enable-debug compile in debugging mode (like --build-type=Debug) --enable-coverage compile with code coverage support (implies debugging mode) + --enable-fuzzing build fuzzing targets --enable-mobile-ipv6 analyze mobile IPv6 features defined by RFC 6275 --enable-perftools enable use of Google perftools (use tcmalloc) --enable-perftools-debug use Google's perftools for debugging @@ -229,6 +230,9 @@ while [ $# -ne 0 ]; do append_cache_entry ENABLE_COVERAGE BOOL true append_cache_entry ENABLE_DEBUG BOOL true ;; + --enable-fuzzing) + append_cache_entry ZEEK_ENABLE_FUZZING BOOL true + ;; --enable-debug) append_cache_entry ENABLE_DEBUG BOOL true ;; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8e533f5733..9072966a54 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -153,6 +153,8 @@ add_subdirectory(iosource) add_subdirectory(logging) add_subdirectory(probabilistic) +add_subdirectory(fuzzers) + ######################################################################## ## bro target @@ -204,12 +206,12 @@ set_source_files_properties(nb_dns.c PROPERTIES COMPILE_FLAGS -fno-strict-aliasing) set(MAIN_SRCS - main.cc digest.cc net_util.cc util.cc module_util.cc zeek-affinity.cc + zeek-setup.cc Anon.cc Attr.cc Base64.cc @@ -316,7 +318,7 @@ set(THIRD_PARTY_SRCS 3rdparty/sqlite3.c ) -set(bro_SRCS +set(zeek_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.c ${BIF_SRCS} ${BINPAC_AUXSRC} @@ -336,9 +338,16 @@ set(bro_SRCS ${MAIN_SRCS} ) -collect_headers(bro_HEADERS ${bro_SRCS}) +collect_headers(zeek_HEADERS ${zeek_SRCS}) -add_executable(zeek ${bro_SRCS} ${bro_HEADERS} ${bro_SUBDIR_LIBS} ${bro_PLUGIN_LIBS}) +add_library(zeek_objs OBJECT ${zeek_SRCS}) + +add_executable(zeek main.cc + $ + ${zeek_HEADERS} + ${bro_SUBDIR_LIBS} + ${bro_PLUGIN_LIBS} +) target_link_libraries(zeek ${zeekdeps} ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS}) if ( NOT "${bro_LINKER_FLAGS}" STREQUAL "" ) @@ -381,12 +390,12 @@ add_dependencies(generate_outputs generate_outputs_stage2a generate_outputs_stag # Build __load__.zeek files for standard *.bif.zeek. bro_bif_create_loader(bif_loader "${bro_BASE_BIF_SCRIPTS}") add_dependencies(bif_loader ${bro_PLUGIN_DEPS} ${bro_SUBDIR_DEPS}) -add_dependencies(zeek bif_loader) +add_dependencies(zeek_objs bif_loader) # Build __load__.zeek files for plugins/*.bif.zeek. bro_bif_create_loader(bif_loader_plugins "${bro_PLUGIN_BIF_SCRIPTS}") add_dependencies(bif_loader_plugins ${bro_PLUGIN_DEPS} ${bro_SUBDIR_DEPS}) -add_dependencies(zeek bif_loader_plugins) +add_dependencies(zeek_objs bif_loader_plugins) # Install *.bif.zeek. install(DIRECTORY ${CMAKE_BINARY_DIR}/scripts/base/bif DESTINATION ${ZEEK_SCRIPT_INSTALL_PATH}/base) diff --git a/src/Options.cc b/src/Options.cc index a68f76595c..d7799cab84 100644 --- a/src/Options.cc +++ b/src/Options.cc @@ -41,6 +41,8 @@ void zeek::Options::filter_supervised_node_options() bare_mode = og.bare_mode; perftools_check_leaks = og.perftools_check_leaks; perftools_profile = og.perftools_profile; + deterministic_mode = og.deterministic_mode; + abort_on_scripting_errors = og.abort_on_scripting_errors; pcap_filter = og.pcap_filter; signature_files = og.signature_files; @@ -93,6 +95,7 @@ void zeek::usage(const char* prog, int code) fprintf(stderr, " -B|--debug | Enable debugging output for selected streams ('-B help' for help)\n"); #endif fprintf(stderr, " -C|--no-checksums | ignore checksums\n"); + fprintf(stderr, " -D|--deterministic | initialize random seeds to zero\n"); fprintf(stderr, " -F|--force-dns | force DNS\n"); fprintf(stderr, " -G|--load-seeds | load seeds from given file\n"); fprintf(stderr, " -H|--save-seeds | save seeds to given file\n"); @@ -202,6 +205,7 @@ zeek::Options zeek::parse_cmdline(int argc, char** argv) {"version", no_argument, nullptr, 'v'}, {"no-checksums", no_argument, nullptr, 'C'}, {"force-dns", no_argument, nullptr, 'F'}, + {"deterministic", no_argument, nullptr, 'D'}, {"load-seeds", required_argument, nullptr, 'G'}, {"save-seeds", required_argument, nullptr, 'H'}, {"print-plugins", no_argument, nullptr, 'N'}, @@ -232,7 +236,7 @@ zeek::Options zeek::parse_cmdline(int argc, char** argv) }; char opts[256]; - safe_strncpy(opts, "B:e:f:G:H:I:i:j::n:p:r:s:T:t:U:w:X:CFNPQSWabdhv", + safe_strncpy(opts, "B:e:f:G:H:I:i:j::n:p:r:s:T:t:U:w:X:CDFNPQSWabdhv", sizeof(opts)); #ifdef USE_PERFTOOLS_DEBUG @@ -329,6 +333,9 @@ zeek::Options zeek::parse_cmdline(int argc, char** argv) case 'C': rval.ignore_checksums = true; break; + case 'D': + rval.deterministic_mode = true; + break; case 'E': rval.pseudo_realtime = 1.0; if ( optarg ) diff --git a/src/Options.h b/src/Options.h index 496a5fa203..2dcbdea853 100644 --- a/src/Options.h +++ b/src/Options.h @@ -53,6 +53,8 @@ struct Options { bool debug_scripts = false; bool perftools_check_leaks = false; bool perftools_profile = false; + bool deterministic_mode = false; + bool abort_on_scripting_errors = false; bool run_unit_tests = false; std::vector doctest_args; diff --git a/src/Reporter.cc b/src/Reporter.cc index e49c9eca75..a4ce65b88b 100644 --- a/src/Reporter.cc +++ b/src/Reporter.cc @@ -32,8 +32,9 @@ int closelog(); Reporter* reporter = nullptr; -Reporter::Reporter() +Reporter::Reporter(bool arg_abort_on_scripting_errors) { + abort_on_scripting_errors = arg_abort_on_scripting_errors; errors = 0; via_events = false; in_error_handler = 0; @@ -157,6 +158,10 @@ void Reporter::ExprRuntimeError(const Expr* expr, const char* fmt, ...) d.Description(), fmt, ap); va_end(ap); PopLocation(); + + if ( abort_on_scripting_errors ) + abort(); + throw InterpreterException(); } @@ -170,6 +175,10 @@ void Reporter::RuntimeError(const Location* location, const char* fmt, ...) DoLog("runtime error", reporter_error, out, nullptr, nullptr, true, true, "", fmt, ap); va_end(ap); PopLocation(); + + if ( abort_on_scripting_errors ) + abort(); + throw InterpreterException(); } diff --git a/src/Reporter.h b/src/Reporter.h index 97f1759963..9224b3e8b6 100644 --- a/src/Reporter.h +++ b/src/Reporter.h @@ -44,7 +44,7 @@ public: using WeirdFlowMap = std::map; using WeirdSet = std::unordered_set; - Reporter(); + Reporter(bool abort_on_scripting_errors); ~Reporter(); // Initialize reporter-sepcific options that are defined in script-layer. @@ -262,6 +262,7 @@ private: bool warnings_to_stderr; bool errors_to_stderr; bool after_zeek_init; + bool abort_on_scripting_errors = false; std::list > locations; diff --git a/src/Sessions.cc b/src/Sessions.cc index 2970a5383f..44f50b6e29 100644 --- a/src/Sessions.cc +++ b/src/Sessions.cc @@ -1107,6 +1107,23 @@ void NetSessions::Drain() } } +void NetSessions::Clear() + { + for ( const auto& entry : tcp_conns ) + Unref(entry.second); + for ( const auto& entry : udp_conns ) + Unref(entry.second); + for ( const auto& entry : icmp_conns ) + Unref(entry.second); + for ( const auto& entry : fragments ) + Unref(entry.second); + + tcp_conns.clear(); + udp_conns.clear(); + icmp_conns.clear(); + fragments.clear(); + } + void NetSessions::GetStats(SessionStats& s) const { s.num_TCP_conns = tcp_conns.size(); diff --git a/src/Sessions.h b/src/Sessions.h index 9cefd6156a..67589d8742 100644 --- a/src/Sessions.h +++ b/src/Sessions.h @@ -72,6 +72,9 @@ public: // that are still active. void Drain(); + // Clears the session maps. + void Clear(); + void GetStats(SessionStats& s) const; void Weird(const char* name, const Packet* pkt, diff --git a/src/broker/Manager.cc b/src/broker/Manager.cc index 8c72ca7cc6..05a10397b2 100644 --- a/src/broker/Manager.cc +++ b/src/broker/Manager.cc @@ -124,10 +124,10 @@ static std::string RenderMessage(const broker::error& e) #endif -Manager::Manager(bool arg_reading_pcaps) +Manager::Manager(bool arg_use_real_time) { bound_port = 0; - reading_pcaps = arg_reading_pcaps; + use_real_time = arg_use_real_time; after_zeek_init = false; peer_count = 0; log_batch_size = 0; @@ -166,7 +166,7 @@ void Manager::InitPostScript() broker::broker_options options; options.disable_ssl = get_option("Broker::disable_ssl")->AsBool(); options.forward = get_option("Broker::forward_messages")->AsBool(); - options.use_real_time = ! reading_pcaps; + options.use_real_time = use_real_time; BrokerConfig config{std::move(options)}; @@ -283,6 +283,14 @@ void Manager::FlushPendingQueries() } } +void Manager::ClearStores() + { + FlushPendingQueries(); + + for ( const auto& [name, handle] : data_stores ) + handle->store.clear(); + } + uint16_t Manager::Listen(const string& addr, uint16_t port) { if ( bstate->endpoint.is_shutdown() ) diff --git a/src/broker/Manager.h b/src/broker/Manager.h index 2a38967458..bbd38f88be 100644 --- a/src/broker/Manager.h +++ b/src/broker/Manager.h @@ -64,7 +64,7 @@ public: /** * Constructor. */ - Manager(bool reading_pcaps); + Manager(bool use_real_time); /** * Destructor. @@ -316,6 +316,11 @@ public: */ size_t FlushLogBuffers(); + /** + * Flushes all pending data store queries and also clears all contents. + */ + void ClearStores(); + /** * @return communication statistics. */ @@ -383,7 +388,7 @@ private: Stats statistics; uint16_t bound_port; - bool reading_pcaps; + bool use_real_time; bool after_zeek_init; int peer_count; diff --git a/src/fuzzers/CMakeLists.txt b/src/fuzzers/CMakeLists.txt new file mode 100644 index 0000000000..6e6d3576b2 --- /dev/null +++ b/src/fuzzers/CMakeLists.txt @@ -0,0 +1,30 @@ +######################################################################## +## Fuzzing targets + +if ( NOT ZEEK_ENABLE_FUZZING ) + return() +endif () + +macro(ADD_FUZZ_TARGET _name) + set(_fuzz_target zeek_fuzzer_${_name}) + set(_fuzz_source ${_name}.cc) + + add_executable(${_fuzz_target} ${_fuzz_source} ${ARGN} + $ + $ + ${zeek_HEADERS} + ${bro_SUBDIR_LIBS} + ${bro_PLUGIN_LIBS}) + + target_link_libraries(${_fuzz_target} ${zeekdeps} + ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS}) + + # TODO: Use LIB_FUZZING_ENGINE env. var. if it exists + set_target_properties(${_fuzz_target} PROPERTIES LINK_FLAGS + "-fsanitize=fuzzer") +endmacro () + +include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}) +add_library(zeek_fuzzer_objs OBJECT FuzzBuffer.cc) + +add_fuzz_target(pop3) diff --git a/src/fuzzers/FuzzBuffer.cc b/src/fuzzers/FuzzBuffer.cc new file mode 100644 index 0000000000..cc25550983 --- /dev/null +++ b/src/fuzzers/FuzzBuffer.cc @@ -0,0 +1,56 @@ +#if !defined(_GNU_SOURCE) +#define _GNU_SOURCE +#endif + +#include + +#include "FuzzBuffer.h" + +bool zeek::FuzzBuffer::Valid() const + { + if ( end - begin < PKT_MAGIC_LEN + 2 ) + return false; + + if ( memcmp(begin, PKT_MAGIC, PKT_MAGIC_LEN) != 0) + return false; + + return true; + } + +int zeek::FuzzBuffer::Next(const unsigned char** chunk, size_t* len, bool* is_orig) + { + if ( begin == end ) + { + *chunk = nullptr; + *len = 0; + return 0; + } + + auto pos = (const unsigned char*)memmem(begin, end - begin, + PKT_MAGIC, PKT_MAGIC_LEN); + + if ( ! pos ) + return -1; + + begin += PKT_MAGIC_LEN; + auto remaining = end - begin; + + if ( remaining < 2 ) + return -2; + + *is_orig = begin[0] & 0x01; + begin += 1; + + *chunk = begin; + + auto next = (const unsigned char*)memmem(begin, end - begin, + PKT_MAGIC, PKT_MAGIC_LEN); + + if ( next ) + begin = next; + else + begin = end; + + *len = begin - *chunk; + return 0; + } diff --git a/src/fuzzers/FuzzBuffer.h b/src/fuzzers/FuzzBuffer.h new file mode 100644 index 0000000000..b677e2ac5c --- /dev/null +++ b/src/fuzzers/FuzzBuffer.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace zeek { + +struct FuzzBuffer { + + static constexpr int PKT_MAGIC_LEN = 4; + static constexpr unsigned char PKT_MAGIC[PKT_MAGIC_LEN + 1] = "\1PKT"; + + FuzzBuffer(const unsigned char* data, size_t size) + : begin(data), end(data + size) + { } + + bool Valid() const; + + int Next(const unsigned char** chunk, size_t* len, bool* is_orig); + + const unsigned char* begin; + const unsigned char* end; +}; + +} // namespace zeek diff --git a/src/fuzzers/fuzz-setup.h b/src/fuzzers/fuzz-setup.h new file mode 100644 index 0000000000..1ad8faff6a --- /dev/null +++ b/src/fuzzers/fuzz-setup.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include "zeek-setup.h" + +#include "Event.h" +#include "Sessions.h" +#include "broker/Manager.h" +#include "file_analysis/Manager.h" + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) + { + zeek::Options options; + options.scripts_to_load.emplace_back("local.zeek"); + options.script_options_to_set.emplace_back("Site::local_nets={10.0.0.0/8}"); + options.script_options_to_set.emplace_back("Log::default_writer=Log::WRITER_NONE"); + options.deterministic_mode = true; + options.abort_on_scripting_errors = true; + + if ( zeek::setup(*argc, *argv, &options).code ) + abort(); + + return 0; + } + +namespace zeek { + +void fuzz_cleanup_one_input() + { + broker_mgr->ClearStores(); + file_mgr->Terminate(); + timer_mgr->Expire(); + + mgr.Drain(); + sessions->Drain(); + mgr.Drain(); + sessions->Clear(); + } + +} // namespace zeek diff --git a/src/fuzzers/pop3.cc b/src/fuzzers/pop3.cc new file mode 100644 index 0000000000..e98afec36f --- /dev/null +++ b/src/fuzzers/pop3.cc @@ -0,0 +1,83 @@ +#include "binpac.h" + +#include "Net.h" +#include "Conn.h" +#include "Sessions.h" +#include "analyzer/Analyzer.h" +#include "analyzer/Manager.h" +#include "analyzer/protocol/pia/PIA.h" +#include "analyzer/protocol/tcp/TCP.h" + +#include "FuzzBuffer.h" +#include "fuzz-setup.h" + +static constexpr auto ZEEK_FUZZ_ANALYZER = "pop3"; + +static Connection* add_connection() + { + static constexpr double network_time_start = 1439471031; + net_update_time(network_time_start); + + Packet p; + ConnID conn_id; + conn_id.src_addr = IPAddr("1.2.3.4"); + conn_id.dst_addr = IPAddr("5.6.7.8"); + conn_id.src_port = htons(23132); + conn_id.dst_port = htons(80); + ConnIDKey key = BuildConnIDKey(conn_id); + Connection* conn = new Connection(sessions, key, network_time_start, + &conn_id, 1, &p, nullptr); + conn->SetTransport(TRANSPORT_TCP); + sessions->Insert(conn); + return conn; + } + +static analyzer::Analyzer* add_analyzer(Connection* conn) + { + analyzer::tcp::TCP_Analyzer* tcp = new analyzer::tcp::TCP_Analyzer(conn); + analyzer::pia::PIA* pia = new analyzer::pia::PIA_TCP(conn); + auto a = analyzer_mgr->InstantiateAnalyzer(ZEEK_FUZZ_ANALYZER, conn); + tcp->AddChildAnalyzer(a); + tcp->AddChildAnalyzer(pia->AsAnalyzer()); + conn->SetRootAnalyzer(tcp, pia); + return a; + } + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) + { + zeek::FuzzBuffer fb{data, size}; + + if ( ! fb.Valid() ) + return 0; + + auto conn = add_connection(); + auto a = add_analyzer(conn); + + const unsigned char* chunk; + size_t chunk_size; + bool is_orig; + + for ( ; ; ) + { + auto err = fb.Next(&chunk, &chunk_size, &is_orig); + + if ( err ) + break; + + if ( chunk_size == 0 ) + break; + + try + { + a->DeliverStream(chunk_size, chunk, is_orig); + } + catch ( const binpac::Exception& e ) + { + } + + mgr.Drain(); + } + + zeek::fuzz_cleanup_one_input(); + return 0; + } diff --git a/src/main.cc b/src/main.cc index b44543173a..7d7e876f41 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,902 +1,26 @@ // See the file "COPYING" in the main distribution directory for copyright. #include "zeek-config.h" +#include "zeek-setup.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef USE_IDMEF -extern "C" { -#include -} -#endif - -#include -#include - -#include "Options.h" -#include "input.h" -#include "DNS_Mgr.h" -#include "Frame.h" -#include "Scope.h" -#include "Event.h" -#include "File.h" -#include "Reporter.h" -#include "Net.h" -#include "NetVar.h" -#include "Var.h" -#include "Timer.h" -#include "Stmt.h" -#include "Desc.h" -#include "Debug.h" -#include "DFA.h" -#include "RuleMatcher.h" -#include "Anon.h" -#include "EventRegistry.h" -#include "Stats.h" -#include "Brofiler.h" -#include "Traverse.h" -#include "Trigger.h" - -#include "supervisor/Supervisor.h" -#include "threading/Manager.h" -#include "input/Manager.h" -#include "logging/Manager.h" -#include "input/readers/raw/Raw.h" -#include "analyzer/Manager.h" -#include "analyzer/Tag.h" -#include "plugin/Manager.h" -#include "file_analysis/Manager.h" -#include "zeekygen/Manager.h" #include "iosource/Manager.h" -#include "broker/Manager.h" - -#include "binpac_bro.h" - -#include "3rdparty/sqlite3.h" - -#define DOCTEST_CONFIG_IMPLEMENT -#include "3rdparty/doctest.h" - -Brofiler brofiler; - -#ifndef HAVE_STRSEP -extern "C" { -char* strsep(char**, const char*); -}; -#endif - -extern "C" { -#include "setsignal.h" -}; - -#ifdef USE_PERFTOOLS_DEBUG -HeapLeakChecker* heap_checker = 0; -int perftools_leaks = 0; -int perftools_profile = 0; -#endif - -DNS_Mgr* dns_mgr; -TimerMgr* timer_mgr; -ValManager* val_mgr = nullptr; -logging::Manager* log_mgr = nullptr; -threading::Manager* thread_mgr = nullptr; -input::Manager* input_mgr = nullptr; -plugin::Manager* plugin_mgr = nullptr; -analyzer::Manager* analyzer_mgr = nullptr; -file_analysis::Manager* file_mgr = nullptr; -zeekygen::Manager* zeekygen_mgr = nullptr; -iosource::Manager* iosource_mgr = nullptr; -bro_broker::Manager* broker_mgr = nullptr; -zeek::Supervisor* zeek::supervisor_mgr = nullptr; -trigger::Manager* trigger_mgr = nullptr; - -std::vector zeek_script_prefixes; -Stmt* stmts; -EventHandlerPtr net_done = nullptr; -RuleMatcher* rule_matcher = nullptr; -EventRegistry* event_registry = nullptr; -ProfileLogger* profiling_logger = nullptr; -ProfileLogger* segment_logger = nullptr; -SampleLogger* sample_logger = nullptr; -int signal_val = 0; -extern char version[]; -const char* command_line_policy = nullptr; -vector params; -set requested_plugins; -const char* proc_status_file = nullptr; - -OpaqueType* md5_type = nullptr; -OpaqueType* sha1_type = nullptr; -OpaqueType* sha256_type = nullptr; -OpaqueType* entropy_type = nullptr; -OpaqueType* cardinality_type = nullptr; -OpaqueType* topk_type = nullptr; -OpaqueType* bloomfilter_type = nullptr; -OpaqueType* x509_opaque_type = nullptr; -OpaqueType* ocsp_resp_opaque_type = nullptr; -OpaqueType* paraglob_type = nullptr; - -// Keep copy of command line -int bro_argc; -char** bro_argv; - -const char* zeek_version() - { -#ifdef DEBUG - static char* debug_version = nullptr; - - if ( ! debug_version ) - { - int n = strlen(version) + sizeof("-debug") + 1; - debug_version = new char[n]; - snprintf(debug_version, n, "%s%s", version, "-debug"); - } - - return debug_version; -#else - return version; -#endif - } - -static std::vector to_cargs(const std::vector& args) - { - std::vector rval; - rval.reserve(args.size()); - - for ( const auto& arg : args ) - rval.emplace_back(arg.data()); - - return rval; - } - -bool show_plugins(int level) - { - plugin::Manager::plugin_list plugins = plugin_mgr->ActivePlugins(); - - if ( ! plugins.size() ) - { - printf("No plugins registered, not even any built-ins. This is probably a bug.\n"); - return false; - } - - ODesc d; - - if ( level == 1 ) - d.SetShort(); - - int count = 0; - - for ( plugin::Manager::plugin_list::const_iterator i = plugins.begin(); i != plugins.end(); i++ ) - { - if ( requested_plugins.size() - && requested_plugins.find((*i)->Name()) == requested_plugins.end() ) - continue; - - (*i)->Describe(&d); - - if ( ! d.IsShort() ) - d.Add("\n"); - - ++count; - } - - printf("%s", d.Description()); - - plugin::Manager::inactive_plugin_list inactives = plugin_mgr->InactivePlugins(); - - if ( inactives.size() && ! requested_plugins.size() ) - { - printf("\nInactive dynamic plugins:\n"); - - for ( plugin::Manager::inactive_plugin_list::const_iterator i = inactives.begin(); i != inactives.end(); i++ ) - { - string name = (*i).first; - string path = (*i).second; - printf(" %s (%s)\n", name.c_str(), path.c_str()); - } - } - - return count != 0; - } - -void done_with_network() - { - set_processing_status("TERMINATING", "done_with_network"); - - // Cancel any pending alarms (watchdog, in particular). - (void) alarm(0); - - if ( net_done ) - { - mgr.Drain(); - // Don't propagate this event to remote clients. - mgr.Dispatch(new Event(net_done, - {make_intrusive(timer_mgr->Time(), TYPE_TIME)}), - true); - } - - if ( profiling_logger ) - profiling_logger->Log(); - - terminating = true; - - analyzer_mgr->Done(); - timer_mgr->Expire(); - dns_mgr->Flush(); - mgr.Drain(); - mgr.Drain(); - - net_finish(1); - -#ifdef USE_PERFTOOLS_DEBUG - - if ( perftools_profile ) - { - HeapProfilerDump("post net_run"); - HeapProfilerStop(); - } - - if ( heap_checker && ! heap_checker->NoLeaks() ) - { - fprintf(stderr, "Memory leaks - aborting.\n"); - abort(); - } -#endif - - ZEEK_LSAN_DISABLE(); - } - -void terminate_bro() - { - set_processing_status("TERMINATING", "terminate_bro"); - - terminating = true; - - iosource_mgr->Wakeup("terminate_bro"); - - // File analysis termination may produce events, so do it early on in - // the termination process. - file_mgr->Terminate(); - - brofiler.WriteStats(); - - EventHandlerPtr zeek_done = internal_handler("zeek_done"); - if ( zeek_done ) - mgr.Enqueue(zeek_done, zeek::Args{}); - - timer_mgr->Expire(); - mgr.Drain(); - - if ( profiling_logger ) - { - // FIXME: There are some occasional crashes in the memory - // allocation code when killing Bro. Disabling this for now. - if ( ! (signal_val == SIGTERM || signal_val == SIGINT) ) - profiling_logger->Log(); - - delete profiling_logger; - } - - mgr.Drain(); - - notifier::registry.Terminate(); - log_mgr->Terminate(); - input_mgr->Terminate(); - thread_mgr->Terminate(); - broker_mgr->Terminate(); - dns_mgr->Terminate(); - - mgr.Drain(); - - plugin_mgr->FinishPlugins(); - - delete zeekygen_mgr; - delete analyzer_mgr; - delete file_mgr; - // broker_mgr, timer_mgr, and supervisor are deleted via iosource_mgr - delete iosource_mgr; - delete event_registry; - delete log_mgr; - delete reporter; - delete plugin_mgr; - delete val_mgr; - - // free the global scope - pop_scope(); - - reporter = nullptr; - } - -void zeek_terminate_loop(const char* reason) - { - set_processing_status("TERMINATING", reason); - reporter->Info("%s", reason); - - net_get_final_stats(); - done_with_network(); - net_delete(); - - terminate_bro(); - - // Close files after net_delete(), because net_delete() - // might write to connection content files. - BroFile::CloseOpenFiles(); - - delete rule_matcher; - - exit(0); - } - -RETSIGTYPE sig_handler(int signo) - { - set_processing_status("TERMINATING", "sig_handler"); - signal_val = signo; - - if ( ! terminating ) - iosource_mgr->Wakeup("sig_handler"); - - return RETSIGVAL; - } - -static void atexit_handler() - { - set_processing_status("TERMINATED", "atexit"); - } - -static void bro_new_handler() - { - out_of_memory("new"); - } - -static std::vector get_script_signature_files() - { - std::vector rval; - - // Parse rule files defined on the script level. - char* script_signature_files = - copy_string(internal_val("signature_files")->AsString()->CheckString()); - - char* tmp = script_signature_files; - char* s; - while ( (s = strsep(&tmp, " \t")) ) - if ( *s ) - rval.emplace_back(s); - - delete [] script_signature_files; - return rval; - } - -static std::string get_exe_path(const std::string& invocation) - { - if ( invocation.empty() ) - return ""; - - if ( invocation[0] == '/' || invocation[0] == '~' ) - // Absolute path - return invocation; - - if ( invocation.find('/') != std::string::npos ) - { - // Relative path - char cwd[PATH_MAX]; - - if ( ! getcwd(cwd, sizeof(cwd)) ) - { - fprintf(stderr, "failed to get current directory: %s\n", - strerror(errno)); - exit(1); - } - - return std::string(cwd) + "/" + invocation; - } - - auto path = getenv("PATH"); - - if ( ! path ) - return ""; - - return find_file(invocation, path); - } +#include "supervisor/Supervisor.h" +#include "Net.h" int main(int argc, char** argv) { - ZEEK_LSAN_DISABLE(); - std::set_new_handler(bro_new_handler); + auto time_start = current_time(true); + auto setup_result = zeek::setup(argc, argv); - auto zeek_exe_path = get_exe_path(argv[0]); + if ( setup_result.code ) + return setup_result.code; - if ( zeek_exe_path.empty() ) - { - fprintf(stderr, "failed to get path to executable '%s'", argv[0]); - exit(1); - } + auto& options = setup_result.options; + auto do_net_run = iosource_mgr->Size() > 0 || + have_pending_timers || + BifConst::exit_only_after_terminate; - bro_argc = argc; - bro_argv = new char* [argc]; - - for ( int i = 0; i < argc; i++ ) - bro_argv[i] = copy_string(argv[i]); - - auto options = zeek::parse_cmdline(argc, argv); - - if ( options.print_usage ) - zeek::usage(argv[0], 0); - - if ( options.print_version ) - { - fprintf(stdout, "%s version %s\n", argv[0], zeek_version()); - exit(0); - } - - if ( options.run_unit_tests ) - { - doctest::Context context; - auto dargs = to_cargs(options.doctest_args); - context.applyCommandLine(dargs.size(), dargs.data()); - ZEEK_LSAN_ENABLE(); - return context.run(); - } - - auto stem_state = zeek::Supervisor::CreateStem(options.supervisor_mode); - - if ( zeek::Supervisor::ThisNode() ) - zeek::Supervisor::ThisNode()->Init(&options); - - double time_start = current_time(true); - - brofiler.ReadStats(); - - auto dns_type = options.dns_mode; - - if ( dns_type == DNS_DEFAULT && zeek::fake_dns() ) - dns_type = DNS_FAKE; - - RETSIGTYPE (*oldhandler)(int); - - zeek_script_prefixes = options.script_prefixes; - auto zeek_prefixes = zeekenv("ZEEK_PREFIXES"); - - if ( zeek_prefixes ) - tokenize_string(zeek_prefixes, ":", &zeek_script_prefixes); - - pseudo_realtime = options.pseudo_realtime; - -#ifdef USE_PERFTOOLS_DEBUG - perftools_leaks = options.perftools_check_leaks; - perftools_profile = options.perftools_profile; -#endif - - if ( options.debug_scripts ) - { - g_policy_debug = options.debug_scripts; - fprintf(stderr, "Zeek script debugging ON.\n"); - } - - if ( options.script_code_to_exec ) - command_line_policy = options.script_code_to_exec->data(); - - if ( options.debug_script_tracing_file ) - { - g_trace_state.SetTraceFile(options.debug_script_tracing_file->data()); - g_trace_state.TraceOn(); - } - - if ( options.process_status_file ) - proc_status_file = options.process_status_file->data(); - - atexit(atexit_handler); - set_processing_status("INITIALIZING", "main"); - - bro_start_time = current_time(true); - - val_mgr = new ValManager(); - reporter = new Reporter(); - thread_mgr = new threading::Manager(); - plugin_mgr = new plugin::Manager(); - -#ifdef DEBUG - if ( options.debug_log_streams ) - { - debug_logger.EnableStreams(options.debug_log_streams->data()); - - if ( getenv("ZEEK_DEBUG_LOG_STDERR") ) - debug_logger.OpenDebugLog(nullptr); - else - debug_logger.OpenDebugLog("debug"); - } -#endif - - if ( options.supervisor_mode ) - { - zeek::Supervisor::Config cfg = {}; - cfg.zeek_exe_path = zeek_exe_path; - options.filter_supervisor_options(); - zeek::supervisor_mgr = new zeek::Supervisor(std::move(cfg), - std::move(*stem_state)); - } - - const char* seed_load_file = zeekenv("ZEEK_SEED_FILE"); - - if ( options.random_seed_input_file ) - seed_load_file = options.random_seed_input_file->data(); - - init_random_seed((seed_load_file && *seed_load_file ? seed_load_file : nullptr), - options.random_seed_output_file ? options.random_seed_output_file->data() : nullptr); - // DEBUG_MSG("HMAC key: %s\n", md5_digest_print(shared_hmac_md5_key)); - init_hash_function(); - - ERR_load_crypto_strings(); - OPENSSL_add_all_algorithms_conf(); - SSL_library_init(); - SSL_load_error_strings(); - - // FIXME: On systems that don't provide /dev/urandom, OpenSSL doesn't - // seed the PRNG. We should do this here (but at least Linux, FreeBSD - // and Solaris provide /dev/urandom). - - int r = sqlite3_initialize(); - - if ( r != SQLITE_OK ) - reporter->Error("Failed to initialize sqlite3: %s", sqlite3_errstr(r)); - -#ifdef USE_IDMEF - char* libidmef_dtd_path_cstr = new char[options.libidmef_dtd_file.size() + 1]; - safe_strncpy(libidmef_dtd_path_cstr, options.libidmef_dtd_file.data(), - options.libidmef_dtd_file.size()); - globalsInit(libidmef_dtd_path_cstr); // Init LIBIDMEF globals - createCurrentDoc("1.0"); // Set a global XML document -#endif - - timer_mgr = new PQ_TimerMgr(); - - auto zeekygen_cfg = options.zeekygen_config_file.value_or(""); - zeekygen_mgr = new zeekygen::Manager(zeekygen_cfg, bro_argv[0]); - - add_essential_input_file("base/init-bare.zeek"); - add_essential_input_file("base/init-frameworks-and-bifs.zeek"); - - if ( ! options.bare_mode ) - add_input_file("base/init-default.zeek"); - - plugin_mgr->SearchDynamicPlugins(bro_plugin_path()); - - if ( options.plugins_to_load.empty() && options.scripts_to_load.empty() && - options.script_options_to_set.empty() && - ! options.pcap_file && ! options.interface && - ! options.identifier_to_print && - ! command_line_policy && ! options.print_plugins && - ! options.supervisor_mode && ! zeek::Supervisor::ThisNode() ) - add_input_file("-"); - - for ( const auto& script_option : options.script_options_to_set ) - params.push_back(script_option); - - for ( const auto& plugin : options.plugins_to_load ) - requested_plugins.insert(plugin); - - for ( const auto& script : options.scripts_to_load ) - add_input_file(script.data()); - - push_scope(nullptr, nullptr); - - dns_mgr = new DNS_Mgr(dns_type); - - // It would nice if this were configurable. This is similar to the - // chicken and the egg problem. It would be configurable by parsing - // policy, but we can't parse policy without DNS resolution. - dns_mgr->SetDir(".state"); - - iosource_mgr = new iosource::Manager(); - event_registry = new EventRegistry(); - analyzer_mgr = new analyzer::Manager(); - log_mgr = new logging::Manager(); - input_mgr = new input::Manager(); - file_mgr = new file_analysis::Manager(); - broker_mgr = new bro_broker::Manager(options.pcap_file.has_value()); - trigger_mgr = new trigger::Manager(); - - plugin_mgr->InitPreScript(); - analyzer_mgr->InitPreScript(); - file_mgr->InitPreScript(); - zeekygen_mgr->InitPreScript(); - - bool missing_plugin = false; - - for ( set::const_iterator i = requested_plugins.begin(); - i != requested_plugins.end(); i++ ) - { - if ( ! plugin_mgr->ActivateDynamicPlugin(*i) ) - missing_plugin = true; - } - - if ( missing_plugin ) - reporter->FatalError("Failed to activate requested dynamic plugin(s)."); - - plugin_mgr->ActivateDynamicPlugins(! options.bare_mode); - - init_event_handlers(); - - md5_type = new OpaqueType("md5"); - sha1_type = new OpaqueType("sha1"); - sha256_type = new OpaqueType("sha256"); - entropy_type = new OpaqueType("entropy"); - cardinality_type = new OpaqueType("cardinality"); - topk_type = new OpaqueType("topk"); - bloomfilter_type = new OpaqueType("bloomfilter"); - x509_opaque_type = new OpaqueType("x509"); - ocsp_resp_opaque_type = new OpaqueType("ocsp_resp"); - paraglob_type = new OpaqueType("paraglob"); - - // The leak-checker tends to produce some false - // positives (memory which had already been - // allocated before we start the checking is - // nevertheless reported; see perftools docs), thus - // we suppress some messages here. - -#ifdef USE_PERFTOOLS_DEBUG - { - HeapLeakChecker::Disabler disabler; -#endif - - is_parsing = true; - yyparse(); - is_parsing = false; - - RecordVal::DoneParsing(); - TableVal::DoneParsing(); - - init_general_global_var(); - init_net_var(); - init_builtin_funcs_subdirs(); - - // Must come after plugin activation (and also after hash - // initialization). - binpac::FlowBuffer::Policy flowbuffer_policy; - flowbuffer_policy.max_capacity = global_scope()->Lookup( - "BinPAC::flowbuffer_capacity_max")->ID_Val()->AsCount(); - flowbuffer_policy.min_capacity = global_scope()->Lookup( - "BinPAC::flowbuffer_capacity_min")->ID_Val()->AsCount(); - flowbuffer_policy.contract_threshold = global_scope()->Lookup( - "BinPAC::flowbuffer_contract_threshold")->ID_Val()->AsCount(); - binpac::init(&flowbuffer_policy); - - plugin_mgr->InitBifs(); - - if ( reporter->Errors() > 0 ) - exit(1); - - iosource_mgr->InitPostScript(); - plugin_mgr->InitPostScript(); - zeekygen_mgr->InitPostScript(); - broker_mgr->InitPostScript(); - timer_mgr->InitPostScript(); - mgr.InitPostScript(); - - if ( zeek::supervisor_mgr ) - zeek::supervisor_mgr->InitPostScript(); - - if ( options.print_plugins ) - { - bool success = show_plugins(options.print_plugins); - exit(success ? 0 : 1); - } - - analyzer_mgr->InitPostScript(); - file_mgr->InitPostScript(); - dns_mgr->InitPostScript(); - - if ( options.parse_only ) - { - int rc = (reporter->Errors() > 0 ? 1 : 0); - exit(rc); - } - -#ifdef USE_PERFTOOLS_DEBUG - } -#endif - - if ( reporter->Errors() > 0 ) - { - delete dns_mgr; - exit(1); - } - - reporter->InitOptions(); - zeekygen_mgr->GenerateDocs(); - - if ( options.pcap_filter ) - { - ID* id = global_scope()->Lookup("cmd_line_bpf_filter"); - - if ( ! id ) - reporter->InternalError("global cmd_line_bpf_filter not defined"); - - id->SetVal(make_intrusive(*options.pcap_filter)); - } - - auto all_signature_files = options.signature_files; - - // Append signature files defined in "signature_files" script option - for ( auto&& sf : get_script_signature_files() ) - all_signature_files.emplace_back(std::move(sf)); - - // Append signature files defined in @load-sigs - for ( const auto& sf : sig_files ) - all_signature_files.emplace_back(sf); - - if ( ! all_signature_files.empty() ) - { - rule_matcher = new RuleMatcher(options.signature_re_level); - if ( ! rule_matcher->ReadFiles(all_signature_files) ) - { - delete dns_mgr; - exit(1); - } - - if ( options.print_signature_debug_info ) - rule_matcher->PrintDebug(); - - file_mgr->InitMagic(); - } - - if ( g_policy_debug ) - // ### Add support for debug command file. - dbg_init_debugger(nullptr); - - if ( ! options.pcap_file && ! options.interface ) - { - Val* interfaces_val = internal_val("interfaces"); - if ( interfaces_val ) - { - char* interfaces_str = - interfaces_val->AsString()->Render(); - - if ( interfaces_str[0] != '\0' ) - options.interface = interfaces_str; - - delete [] interfaces_str; - } - } - - if ( dns_type != DNS_PRIME ) - net_init(options.interface, options.pcap_file, options.pcap_output_file, options.use_watchdog); - - net_done = internal_handler("net_done"); - - if ( ! g_policy_debug ) - { - (void) setsignal(SIGTERM, sig_handler); - (void) setsignal(SIGINT, sig_handler); - (void) setsignal(SIGPIPE, SIG_IGN); - } - - // Cooperate with nohup(1). - if ( (oldhandler = setsignal(SIGHUP, sig_handler)) != SIG_DFL ) - (void) setsignal(SIGHUP, oldhandler); - - if ( dns_type == DNS_PRIME ) - { - dns_mgr->Verify(); - dns_mgr->Resolve(); - - if ( ! dns_mgr->Save() ) - reporter->FatalError("can't update DNS cache"); - - mgr.Drain(); - delete dns_mgr; - exit(0); - } - - // Print the ID. - if ( options.identifier_to_print ) - { - ID* id = global_scope()->Lookup(*options.identifier_to_print); - if ( ! id ) - reporter->FatalError("No such ID: %s\n", options.identifier_to_print->data()); - - ODesc desc; - desc.SetQuotes(true); - desc.SetIncludeStats(true); - id->DescribeExtended(&desc); - - fprintf(stdout, "%s\n", desc.Description()); - exit(0); - } - - if ( profiling_interval > 0 ) - { - profiling_logger = new ProfileLogger(profiling_file->AsFile(), - profiling_interval); - - if ( segment_profiling ) - segment_logger = profiling_logger; - } - - if ( ! reading_live && ! reading_traces ) - // Set up network_time to track real-time, since - // we don't have any other source for it. - net_update_time(current_time()); - - EventHandlerPtr zeek_init = internal_handler("zeek_init"); - if ( zeek_init ) //### this should be a function - mgr.Enqueue(zeek_init, zeek::Args{}); - - EventRegistry::string_list dead_handlers = - event_registry->UnusedHandlers(); - - if ( ! dead_handlers.empty() && check_for_unused_event_handlers ) - { - for ( const string& handler : dead_handlers ) - reporter->Warning("event handler never invoked: %s", handler.c_str()); - } - - // Enable LeakSanitizer before zeek_init() and even before executing - // top-level statements. Even though it's not bad if a leak happens only - // once at initialization, we have to assume that script-layer code causing - // such a leak can be placed in any arbitrary event handler and potentially - // cause more severe problems. - ZEEK_LSAN_ENABLE(); - - if ( stmts ) - { - stmt_flow_type flow; - Frame f(current_scope()->Length(), nullptr, nullptr); - g_frame_stack.push_back(&f); - - try - { - stmts->Exec(&f, flow); - } - catch ( InterpreterException& ) - { - reporter->FatalError("failed to execute script statements at top-level scope"); - } - - g_frame_stack.pop_back(); - } - - if ( options.ignore_checksums ) - ignore_checksums = 1; - - if ( zeek_script_loaded ) - { - // Queue events reporting loaded scripts. - for ( std::list::iterator i = files_scanned.begin(); i != files_scanned.end(); i++ ) - { - if ( i->skipped ) - continue; - - mgr.Enqueue(zeek_script_loaded, - make_intrusive(i->name.c_str()), - IntrusivePtr{AdoptRef{}, val_mgr->GetCount(i->include_level)} - ); - } - } - - reporter->ReportViaEvents(true); - - // Drain the event queue here to support the protocols framework configuring DPM - mgr.Drain(); - - if ( reporter->Errors() > 0 && ! zeekenv("ZEEK_ALLOW_INIT_ERRORS") ) - reporter->FatalError("errors occurred while initializing"); - - broker_mgr->ZeekInitDone(); - reporter->ZeekInitDone(); - analyzer_mgr->DumpDebug(); - - have_pending_timers = ! reading_traces && timer_mgr->Size() > 0; - - if ( iosource_mgr->Size() > 0 || - have_pending_timers || - BifConst::exit_only_after_terminate ) + if ( do_net_run ) { if ( profiling_logger ) profiling_logger->Log(); @@ -952,24 +76,7 @@ int main(int argc, char** argv) (mem_net_done_total - mem_net_start_total) / 1024 / 1024, (mem_net_done_malloced - mem_net_start_malloced) / 1024 / 1024); } - - done_with_network(); - net_delete(); } - terminate_bro(); - - sqlite3_shutdown(); - - ERR_free_strings(); - EVP_cleanup(); - CRYPTO_cleanup_all_ex_data(); - - // Close files after net_delete(), because net_delete() - // might write to connection content files. - BroFile::CloseOpenFiles(); - - delete rule_matcher; - - return 0; + return zeek::cleanup(do_net_run); } diff --git a/src/util.cc b/src/util.cc index b6293307bf..1293c34804 100644 --- a/src/util.cc +++ b/src/util.cc @@ -1094,7 +1094,8 @@ void bro_srandom(unsigned int seed) srandom(seed); } -void init_random_seed(const char* read_file, const char* write_file) +void init_random_seed(const char* read_file, const char* write_file, + bool use_empty_seeds) { static const int bufsiz = 20; uint32_t buf[bufsiz]; @@ -1111,6 +1112,8 @@ void init_random_seed(const char* read_file, const char* write_file) else seeds_done = true; } + else if ( use_empty_seeds ) + seeds_done = true; #ifdef HAVE_GETRANDOM if ( ! seeds_done ) diff --git a/src/util.h b/src/util.h index 66d65ba622..1353a36cdd 100644 --- a/src/util.h +++ b/src/util.h @@ -208,13 +208,13 @@ extern uint8_t shared_siphash_key[SIPHASH_KEYLEN]; extern void hmac_md5(size_t size, const unsigned char* bytes, unsigned char digest[16]); -// Initializes RNGs for bro_random() and MD5 usage. If seed is given, then -// it is used (to provide determinism). If load_file is given, the seeds -// (both random & MD5) are loaded from that file. This takes precedence -// over the "seed" argument. If write_file is given, the seeds are written -// to that file. -// -extern void init_random_seed(const char* load_file, const char* write_file); +// Initializes RNGs for bro_random() and MD5 usage. If load_file is given, +// the seeds (both random & MD5) are loaded from that file. This takes +// precedence over the "use_empty_seeds" argument, which just +// zero-initializes all seed values. If write_file is given, the seeds are +// written to that file. +extern void init_random_seed(const char* load_file, const char* write_file, + bool use_empty_seeds); // Retrieves the initial seed computed after the very first call to // init_random_seed(). Repeated calls to init_random_seed() will not affect diff --git a/src/zeek-setup.cc b/src/zeek-setup.cc new file mode 100644 index 0000000000..8cc392e4fb --- /dev/null +++ b/src/zeek-setup.cc @@ -0,0 +1,922 @@ +// See the file "COPYING" in the main distribution directory for copyright. + +#include "zeek-config.h" +#include "zeek-setup.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USE_IDMEF +extern "C" { +#include +} +#endif + +#include +#include + +#include "Options.h" +#include "input.h" +#include "DNS_Mgr.h" +#include "Frame.h" +#include "Scope.h" +#include "Event.h" +#include "File.h" +#include "Reporter.h" +#include "Net.h" +#include "NetVar.h" +#include "Var.h" +#include "Timer.h" +#include "Stmt.h" +#include "Desc.h" +#include "Debug.h" +#include "DFA.h" +#include "RuleMatcher.h" +#include "Anon.h" +#include "EventRegistry.h" +#include "Stats.h" +#include "Brofiler.h" +#include "Traverse.h" +#include "Trigger.h" + +#include "supervisor/Supervisor.h" +#include "threading/Manager.h" +#include "input/Manager.h" +#include "logging/Manager.h" +#include "input/readers/raw/Raw.h" +#include "analyzer/Manager.h" +#include "analyzer/Tag.h" +#include "plugin/Manager.h" +#include "file_analysis/Manager.h" +#include "zeekygen/Manager.h" +#include "iosource/Manager.h" +#include "broker/Manager.h" + +#include "binpac_bro.h" + +#include "3rdparty/sqlite3.h" + +#define DOCTEST_CONFIG_IMPLEMENT +#include "3rdparty/doctest.h" + +Brofiler brofiler; + +#ifndef HAVE_STRSEP +extern "C" { +char* strsep(char**, const char*); +}; +#endif + +extern "C" { +#include "setsignal.h" +}; + +#ifdef USE_PERFTOOLS_DEBUG +HeapLeakChecker* heap_checker = 0; +int perftools_leaks = 0; +int perftools_profile = 0; +#endif + +DNS_Mgr* dns_mgr; +TimerMgr* timer_mgr; +ValManager* val_mgr = nullptr; +logging::Manager* log_mgr = nullptr; +threading::Manager* thread_mgr = nullptr; +input::Manager* input_mgr = nullptr; +plugin::Manager* plugin_mgr = nullptr; +analyzer::Manager* analyzer_mgr = nullptr; +file_analysis::Manager* file_mgr = nullptr; +zeekygen::Manager* zeekygen_mgr = nullptr; +iosource::Manager* iosource_mgr = nullptr; +bro_broker::Manager* broker_mgr = nullptr; +zeek::Supervisor* zeek::supervisor_mgr = nullptr; +trigger::Manager* trigger_mgr = nullptr; + +std::vector zeek_script_prefixes; +Stmt* stmts; +EventHandlerPtr net_done = nullptr; +RuleMatcher* rule_matcher = nullptr; +EventRegistry* event_registry = nullptr; +ProfileLogger* profiling_logger = nullptr; +ProfileLogger* segment_logger = nullptr; +SampleLogger* sample_logger = nullptr; +int signal_val = 0; +extern char version[]; +const char* command_line_policy = nullptr; +vector params; +set requested_plugins; +const char* proc_status_file = nullptr; + +OpaqueType* md5_type = nullptr; +OpaqueType* sha1_type = nullptr; +OpaqueType* sha256_type = nullptr; +OpaqueType* entropy_type = nullptr; +OpaqueType* cardinality_type = nullptr; +OpaqueType* topk_type = nullptr; +OpaqueType* bloomfilter_type = nullptr; +OpaqueType* x509_opaque_type = nullptr; +OpaqueType* ocsp_resp_opaque_type = nullptr; +OpaqueType* paraglob_type = nullptr; + +// Keep copy of command line +int bro_argc; +char** bro_argv; + +const char* zeek_version() + { +#ifdef DEBUG + static char* debug_version = nullptr; + + if ( ! debug_version ) + { + int n = strlen(version) + sizeof("-debug") + 1; + debug_version = new char[n]; + snprintf(debug_version, n, "%s%s", version, "-debug"); + } + + return debug_version; +#else + return version; +#endif + } + +static std::vector to_cargs(const std::vector& args) + { + std::vector rval; + rval.reserve(args.size()); + + for ( const auto& arg : args ) + rval.emplace_back(arg.data()); + + return rval; + } + +bool show_plugins(int level) + { + plugin::Manager::plugin_list plugins = plugin_mgr->ActivePlugins(); + + if ( ! plugins.size() ) + { + printf("No plugins registered, not even any built-ins. This is probably a bug.\n"); + return false; + } + + ODesc d; + + if ( level == 1 ) + d.SetShort(); + + int count = 0; + + for ( plugin::Manager::plugin_list::const_iterator i = plugins.begin(); i != plugins.end(); i++ ) + { + if ( requested_plugins.size() + && requested_plugins.find((*i)->Name()) == requested_plugins.end() ) + continue; + + (*i)->Describe(&d); + + if ( ! d.IsShort() ) + d.Add("\n"); + + ++count; + } + + printf("%s", d.Description()); + + plugin::Manager::inactive_plugin_list inactives = plugin_mgr->InactivePlugins(); + + if ( inactives.size() && ! requested_plugins.size() ) + { + printf("\nInactive dynamic plugins:\n"); + + for ( plugin::Manager::inactive_plugin_list::const_iterator i = inactives.begin(); i != inactives.end(); i++ ) + { + string name = (*i).first; + string path = (*i).second; + printf(" %s (%s)\n", name.c_str(), path.c_str()); + } + } + + return count != 0; + } + +void done_with_network() + { + set_processing_status("TERMINATING", "done_with_network"); + + // Cancel any pending alarms (watchdog, in particular). + (void) alarm(0); + + if ( net_done ) + { + mgr.Drain(); + // Don't propagate this event to remote clients. + mgr.Dispatch(new Event(net_done, + {make_intrusive(timer_mgr->Time(), TYPE_TIME)}), + true); + } + + if ( profiling_logger ) + profiling_logger->Log(); + + terminating = true; + + analyzer_mgr->Done(); + timer_mgr->Expire(); + dns_mgr->Flush(); + mgr.Drain(); + mgr.Drain(); + + net_finish(1); + +#ifdef USE_PERFTOOLS_DEBUG + + if ( perftools_profile ) + { + HeapProfilerDump("post net_run"); + HeapProfilerStop(); + } + + if ( heap_checker && ! heap_checker->NoLeaks() ) + { + fprintf(stderr, "Memory leaks - aborting.\n"); + abort(); + } +#endif + + ZEEK_LSAN_DISABLE(); + } + +void terminate_bro() + { + set_processing_status("TERMINATING", "terminate_bro"); + + terminating = true; + + iosource_mgr->Wakeup("terminate_bro"); + + // File analysis termination may produce events, so do it early on in + // the termination process. + file_mgr->Terminate(); + + brofiler.WriteStats(); + + EventHandlerPtr zeek_done = internal_handler("zeek_done"); + if ( zeek_done ) + mgr.Enqueue(zeek_done, zeek::Args{}); + + timer_mgr->Expire(); + mgr.Drain(); + + if ( profiling_logger ) + { + // FIXME: There are some occasional crashes in the memory + // allocation code when killing Bro. Disabling this for now. + if ( ! (signal_val == SIGTERM || signal_val == SIGINT) ) + profiling_logger->Log(); + + delete profiling_logger; + } + + mgr.Drain(); + + notifier::registry.Terminate(); + log_mgr->Terminate(); + input_mgr->Terminate(); + thread_mgr->Terminate(); + broker_mgr->Terminate(); + dns_mgr->Terminate(); + + mgr.Drain(); + + plugin_mgr->FinishPlugins(); + + delete zeekygen_mgr; + delete analyzer_mgr; + delete file_mgr; + // broker_mgr, timer_mgr, and supervisor are deleted via iosource_mgr + delete iosource_mgr; + delete event_registry; + delete log_mgr; + delete reporter; + delete plugin_mgr; + delete val_mgr; + + // free the global scope + pop_scope(); + + reporter = nullptr; + } + +void zeek_terminate_loop(const char* reason) + { + set_processing_status("TERMINATING", reason); + reporter->Info("%s", reason); + + net_get_final_stats(); + done_with_network(); + net_delete(); + + terminate_bro(); + + // Close files after net_delete(), because net_delete() + // might write to connection content files. + BroFile::CloseOpenFiles(); + + delete rule_matcher; + + exit(0); + } + +RETSIGTYPE sig_handler(int signo) + { + set_processing_status("TERMINATING", "sig_handler"); + signal_val = signo; + + if ( ! terminating ) + iosource_mgr->Wakeup("sig_handler"); + + return RETSIGVAL; + } + +static void atexit_handler() + { + set_processing_status("TERMINATED", "atexit"); + } + +static void bro_new_handler() + { + out_of_memory("new"); + } + +static std::vector get_script_signature_files() + { + std::vector rval; + + // Parse rule files defined on the script level. + char* script_signature_files = + copy_string(internal_val("signature_files")->AsString()->CheckString()); + + char* tmp = script_signature_files; + char* s; + while ( (s = strsep(&tmp, " \t")) ) + if ( *s ) + rval.emplace_back(s); + + delete [] script_signature_files; + return rval; + } + +static std::string get_exe_path(const std::string& invocation) + { + if ( invocation.empty() ) + return ""; + + if ( invocation[0] == '/' || invocation[0] == '~' ) + // Absolute path + return invocation; + + if ( invocation.find('/') != std::string::npos ) + { + // Relative path + char cwd[PATH_MAX]; + + if ( ! getcwd(cwd, sizeof(cwd)) ) + { + fprintf(stderr, "failed to get current directory: %s\n", + strerror(errno)); + exit(1); + } + + return std::string(cwd) + "/" + invocation; + } + + auto path = getenv("PATH"); + + if ( ! path ) + return ""; + + return find_file(invocation, path); + } + +zeek::SetupResult zeek::setup(int argc, char** argv, zeek::Options* zopts) + { + ZEEK_LSAN_DISABLE(); + std::set_new_handler(bro_new_handler); + + auto zeek_exe_path = get_exe_path(argv[0]); + + if ( zeek_exe_path.empty() ) + { + fprintf(stderr, "failed to get path to executable '%s'", argv[0]); + exit(1); + } + + bro_argc = argc; + bro_argv = new char* [argc]; + + for ( int i = 0; i < argc; i++ ) + bro_argv[i] = copy_string(argv[i]); + + auto options = zopts ? *zopts : zeek::parse_cmdline(argc, argv); + + if ( options.print_usage ) + zeek::usage(argv[0], 0); + + if ( options.print_version ) + { + fprintf(stdout, "%s version %s\n", argv[0], zeek_version()); + exit(0); + } + + if ( options.run_unit_tests ) + { + doctest::Context context; + auto dargs = to_cargs(options.doctest_args); + context.applyCommandLine(dargs.size(), dargs.data()); + ZEEK_LSAN_ENABLE(); + return {context.run(), std::move(options)}; + } + + auto stem_state = zeek::Supervisor::CreateStem(options.supervisor_mode); + + if ( zeek::Supervisor::ThisNode() ) + zeek::Supervisor::ThisNode()->Init(&options); + + brofiler.ReadStats(); + + auto dns_type = options.dns_mode; + + if ( dns_type == DNS_DEFAULT && zeek::fake_dns() ) + dns_type = DNS_FAKE; + + RETSIGTYPE (*oldhandler)(int); + + zeek_script_prefixes = options.script_prefixes; + auto zeek_prefixes = zeekenv("ZEEK_PREFIXES"); + + if ( zeek_prefixes ) + tokenize_string(zeek_prefixes, ":", &zeek_script_prefixes); + + pseudo_realtime = options.pseudo_realtime; + +#ifdef USE_PERFTOOLS_DEBUG + perftools_leaks = options.perftools_check_leaks; + perftools_profile = options.perftools_profile; +#endif + + if ( options.debug_scripts ) + { + g_policy_debug = options.debug_scripts; + fprintf(stderr, "Zeek script debugging ON.\n"); + } + + if ( options.script_code_to_exec ) + command_line_policy = options.script_code_to_exec->data(); + + if ( options.debug_script_tracing_file ) + { + g_trace_state.SetTraceFile(options.debug_script_tracing_file->data()); + g_trace_state.TraceOn(); + } + + if ( options.process_status_file ) + proc_status_file = options.process_status_file->data(); + + atexit(atexit_handler); + set_processing_status("INITIALIZING", "main"); + + bro_start_time = current_time(true); + + val_mgr = new ValManager(); + reporter = new Reporter(options.abort_on_scripting_errors); + thread_mgr = new threading::Manager(); + plugin_mgr = new plugin::Manager(); + +#ifdef DEBUG + if ( options.debug_log_streams ) + { + debug_logger.EnableStreams(options.debug_log_streams->data()); + + if ( getenv("ZEEK_DEBUG_LOG_STDERR") ) + debug_logger.OpenDebugLog(nullptr); + else + debug_logger.OpenDebugLog("debug"); + } +#endif + + if ( options.supervisor_mode ) + { + zeek::Supervisor::Config cfg = {}; + cfg.zeek_exe_path = zeek_exe_path; + options.filter_supervisor_options(); + zeek::supervisor_mgr = new zeek::Supervisor(std::move(cfg), + std::move(*stem_state)); + } + + const char* seed_load_file = zeekenv("ZEEK_SEED_FILE"); + + if ( options.random_seed_input_file ) + seed_load_file = options.random_seed_input_file->data(); + + init_random_seed((seed_load_file && *seed_load_file ? seed_load_file : nullptr), + options.random_seed_output_file ? options.random_seed_output_file->data() : nullptr, + options.deterministic_mode); + // DEBUG_MSG("HMAC key: %s\n", md5_digest_print(shared_hmac_md5_key)); + init_hash_function(); + + ERR_load_crypto_strings(); + OPENSSL_add_all_algorithms_conf(); + SSL_library_init(); + SSL_load_error_strings(); + + // FIXME: On systems that don't provide /dev/urandom, OpenSSL doesn't + // seed the PRNG. We should do this here (but at least Linux, FreeBSD + // and Solaris provide /dev/urandom). + + int r = sqlite3_initialize(); + + if ( r != SQLITE_OK ) + reporter->Error("Failed to initialize sqlite3: %s", sqlite3_errstr(r)); + +#ifdef USE_IDMEF + char* libidmef_dtd_path_cstr = new char[options.libidmef_dtd_file.size() + 1]; + safe_strncpy(libidmef_dtd_path_cstr, options.libidmef_dtd_file.data(), + options.libidmef_dtd_file.size()); + globalsInit(libidmef_dtd_path_cstr); // Init LIBIDMEF globals + createCurrentDoc("1.0"); // Set a global XML document +#endif + + timer_mgr = new PQ_TimerMgr(); + + auto zeekygen_cfg = options.zeekygen_config_file.value_or(""); + zeekygen_mgr = new zeekygen::Manager(zeekygen_cfg, bro_argv[0]); + + add_essential_input_file("base/init-bare.zeek"); + add_essential_input_file("base/init-frameworks-and-bifs.zeek"); + + if ( ! options.bare_mode ) + add_input_file("base/init-default.zeek"); + + plugin_mgr->SearchDynamicPlugins(bro_plugin_path()); + + if ( options.plugins_to_load.empty() && options.scripts_to_load.empty() && + options.script_options_to_set.empty() && + ! options.pcap_file && ! options.interface && + ! options.identifier_to_print && + ! command_line_policy && ! options.print_plugins && + ! options.supervisor_mode && ! zeek::Supervisor::ThisNode() ) + add_input_file("-"); + + for ( const auto& script_option : options.script_options_to_set ) + params.push_back(script_option); + + for ( const auto& plugin : options.plugins_to_load ) + requested_plugins.insert(plugin); + + for ( const auto& script : options.scripts_to_load ) + add_input_file(script.data()); + + push_scope(nullptr, nullptr); + + dns_mgr = new DNS_Mgr(dns_type); + + // It would nice if this were configurable. This is similar to the + // chicken and the egg problem. It would be configurable by parsing + // policy, but we can't parse policy without DNS resolution. + dns_mgr->SetDir(".state"); + + iosource_mgr = new iosource::Manager(); + event_registry = new EventRegistry(); + analyzer_mgr = new analyzer::Manager(); + log_mgr = new logging::Manager(); + input_mgr = new input::Manager(); + file_mgr = new file_analysis::Manager(); + auto broker_real_time = ! options.pcap_file && ! options.deterministic_mode; + broker_mgr = new bro_broker::Manager(broker_real_time); + trigger_mgr = new trigger::Manager(); + + plugin_mgr->InitPreScript(); + analyzer_mgr->InitPreScript(); + file_mgr->InitPreScript(); + zeekygen_mgr->InitPreScript(); + + bool missing_plugin = false; + + for ( set::const_iterator i = requested_plugins.begin(); + i != requested_plugins.end(); i++ ) + { + if ( ! plugin_mgr->ActivateDynamicPlugin(*i) ) + missing_plugin = true; + } + + if ( missing_plugin ) + reporter->FatalError("Failed to activate requested dynamic plugin(s)."); + + plugin_mgr->ActivateDynamicPlugins(! options.bare_mode); + + init_event_handlers(); + + md5_type = new OpaqueType("md5"); + sha1_type = new OpaqueType("sha1"); + sha256_type = new OpaqueType("sha256"); + entropy_type = new OpaqueType("entropy"); + cardinality_type = new OpaqueType("cardinality"); + topk_type = new OpaqueType("topk"); + bloomfilter_type = new OpaqueType("bloomfilter"); + x509_opaque_type = new OpaqueType("x509"); + ocsp_resp_opaque_type = new OpaqueType("ocsp_resp"); + paraglob_type = new OpaqueType("paraglob"); + + // The leak-checker tends to produce some false + // positives (memory which had already been + // allocated before we start the checking is + // nevertheless reported; see perftools docs), thus + // we suppress some messages here. + +#ifdef USE_PERFTOOLS_DEBUG + { + HeapLeakChecker::Disabler disabler; +#endif + + is_parsing = true; + yyparse(); + is_parsing = false; + + RecordVal::DoneParsing(); + TableVal::DoneParsing(); + + init_general_global_var(); + init_net_var(); + init_builtin_funcs_subdirs(); + + // Must come after plugin activation (and also after hash + // initialization). + binpac::FlowBuffer::Policy flowbuffer_policy; + flowbuffer_policy.max_capacity = global_scope()->Lookup( + "BinPAC::flowbuffer_capacity_max")->ID_Val()->AsCount(); + flowbuffer_policy.min_capacity = global_scope()->Lookup( + "BinPAC::flowbuffer_capacity_min")->ID_Val()->AsCount(); + flowbuffer_policy.contract_threshold = global_scope()->Lookup( + "BinPAC::flowbuffer_contract_threshold")->ID_Val()->AsCount(); + binpac::init(&flowbuffer_policy); + + plugin_mgr->InitBifs(); + + if ( reporter->Errors() > 0 ) + exit(1); + + iosource_mgr->InitPostScript(); + plugin_mgr->InitPostScript(); + zeekygen_mgr->InitPostScript(); + broker_mgr->InitPostScript(); + timer_mgr->InitPostScript(); + mgr.InitPostScript(); + + if ( zeek::supervisor_mgr ) + zeek::supervisor_mgr->InitPostScript(); + + if ( options.print_plugins ) + { + bool success = show_plugins(options.print_plugins); + exit(success ? 0 : 1); + } + + analyzer_mgr->InitPostScript(); + file_mgr->InitPostScript(); + dns_mgr->InitPostScript(); + + if ( options.parse_only ) + { + int rc = (reporter->Errors() > 0 ? 1 : 0); + exit(rc); + } + +#ifdef USE_PERFTOOLS_DEBUG + } +#endif + + if ( reporter->Errors() > 0 ) + { + delete dns_mgr; + exit(1); + } + + reporter->InitOptions(); + zeekygen_mgr->GenerateDocs(); + + if ( options.pcap_filter ) + { + ID* id = global_scope()->Lookup("cmd_line_bpf_filter"); + + if ( ! id ) + reporter->InternalError("global cmd_line_bpf_filter not defined"); + + id->SetVal(make_intrusive(*options.pcap_filter)); + } + + auto all_signature_files = options.signature_files; + + // Append signature files defined in "signature_files" script option + for ( auto&& sf : get_script_signature_files() ) + all_signature_files.emplace_back(std::move(sf)); + + // Append signature files defined in @load-sigs + for ( const auto& sf : sig_files ) + all_signature_files.emplace_back(sf); + + if ( ! all_signature_files.empty() ) + { + rule_matcher = new RuleMatcher(options.signature_re_level); + if ( ! rule_matcher->ReadFiles(all_signature_files) ) + { + delete dns_mgr; + exit(1); + } + + if ( options.print_signature_debug_info ) + rule_matcher->PrintDebug(); + + file_mgr->InitMagic(); + } + + if ( g_policy_debug ) + // ### Add support for debug command file. + dbg_init_debugger(nullptr); + + if ( ! options.pcap_file && ! options.interface ) + { + Val* interfaces_val = internal_val("interfaces"); + if ( interfaces_val ) + { + char* interfaces_str = + interfaces_val->AsString()->Render(); + + if ( interfaces_str[0] != '\0' ) + options.interface = interfaces_str; + + delete [] interfaces_str; + } + } + + if ( dns_type != DNS_PRIME ) + net_init(options.interface, options.pcap_file, options.pcap_output_file, options.use_watchdog); + + net_done = internal_handler("net_done"); + + if ( ! g_policy_debug ) + { + (void) setsignal(SIGTERM, sig_handler); + (void) setsignal(SIGINT, sig_handler); + (void) setsignal(SIGPIPE, SIG_IGN); + } + + // Cooperate with nohup(1). + if ( (oldhandler = setsignal(SIGHUP, sig_handler)) != SIG_DFL ) + (void) setsignal(SIGHUP, oldhandler); + + if ( dns_type == DNS_PRIME ) + { + dns_mgr->Verify(); + dns_mgr->Resolve(); + + if ( ! dns_mgr->Save() ) + reporter->FatalError("can't update DNS cache"); + + mgr.Drain(); + delete dns_mgr; + exit(0); + } + + // Print the ID. + if ( options.identifier_to_print ) + { + ID* id = global_scope()->Lookup(*options.identifier_to_print); + if ( ! id ) + reporter->FatalError("No such ID: %s\n", options.identifier_to_print->data()); + + ODesc desc; + desc.SetQuotes(true); + desc.SetIncludeStats(true); + id->DescribeExtended(&desc); + + fprintf(stdout, "%s\n", desc.Description()); + exit(0); + } + + if ( profiling_interval > 0 ) + { + profiling_logger = new ProfileLogger(profiling_file->AsFile(), + profiling_interval); + + if ( segment_profiling ) + segment_logger = profiling_logger; + } + + if ( ! reading_live && ! reading_traces ) + // Set up network_time to track real-time, since + // we don't have any other source for it. + net_update_time(current_time()); + + EventHandlerPtr zeek_init = internal_handler("zeek_init"); + if ( zeek_init ) //### this should be a function + mgr.Enqueue(zeek_init, zeek::Args{}); + + EventRegistry::string_list dead_handlers = + event_registry->UnusedHandlers(); + + if ( ! dead_handlers.empty() && check_for_unused_event_handlers ) + { + for ( const string& handler : dead_handlers ) + reporter->Warning("event handler never invoked: %s", handler.c_str()); + } + + // Enable LeakSanitizer before zeek_init() and even before executing + // top-level statements. Even though it's not bad if a leak happens only + // once at initialization, we have to assume that script-layer code causing + // such a leak can be placed in any arbitrary event handler and potentially + // cause more severe problems. + ZEEK_LSAN_ENABLE(); + + if ( stmts ) + { + stmt_flow_type flow; + Frame f(current_scope()->Length(), nullptr, nullptr); + g_frame_stack.push_back(&f); + + try + { + stmts->Exec(&f, flow); + } + catch ( InterpreterException& ) + { + reporter->FatalError("failed to execute script statements at top-level scope"); + } + + g_frame_stack.pop_back(); + } + + if ( options.ignore_checksums ) + ignore_checksums = 1; + + if ( zeek_script_loaded ) + { + // Queue events reporting loaded scripts. + for ( std::list::iterator i = files_scanned.begin(); i != files_scanned.end(); i++ ) + { + if ( i->skipped ) + continue; + + mgr.Enqueue(zeek_script_loaded, + make_intrusive(i->name.c_str()), + IntrusivePtr{AdoptRef{}, val_mgr->GetCount(i->include_level)} + ); + } + } + + reporter->ReportViaEvents(true); + + // Drain the event queue here to support the protocols framework configuring DPM + mgr.Drain(); + + if ( reporter->Errors() > 0 && ! zeekenv("ZEEK_ALLOW_INIT_ERRORS") ) + reporter->FatalError("errors occurred while initializing"); + + broker_mgr->ZeekInitDone(); + reporter->ZeekInitDone(); + analyzer_mgr->DumpDebug(); + + have_pending_timers = ! reading_traces && timer_mgr->Size() > 0; + + return {0, std::move(options)}; + } + +int zeek::cleanup(bool did_net_run) + { + if ( did_net_run ) + done_with_network(); + + net_delete(); + terminate_bro(); + + sqlite3_shutdown(); + + ERR_free_strings(); + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); + + // Close files after net_delete(), because net_delete() + // might write to connection content files. + BroFile::CloseOpenFiles(); + + delete rule_matcher; + + return 0; + } diff --git a/src/zeek-setup.h b/src/zeek-setup.h new file mode 100644 index 0000000000..303ded4abd --- /dev/null +++ b/src/zeek-setup.h @@ -0,0 +1,31 @@ +// See the file "COPYING" in the main distribution directory for copyright. + +#pragma once + +#include "Options.h" + +namespace zeek { + +struct SetupResult { + int code = 0; + zeek::Options options; +}; + +/** + * Initializes Zeek's global state. + * @param argc the argument count (same semantics as main function) + * @param argv the argument strings (same semantics as main function) + * @param options if provided, those options are used instead of + * deriving them by parsing the "argv" list. The "argv" list still + * needs to be provided regardless since some functionality requires + * it, particularly, several things use the value of argv[0]. + */ +SetupResult setup(int argc, char** argv, zeek::Options* options = nullptr); + +/** + * Cleans up Zeek's global state. + * @param did_net_run whether the net_run() was called. + */ +int cleanup(bool did_net_run); + +} From 78b0b2183d949409d3e2e1673cb9950fb09855cf Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Thu, 23 Apr 2020 20:09:22 -0700 Subject: [PATCH 02/17] Add standalone driver for fuzz targets Useful for cases that don't need to use a fuzzing engine, but just run the fuzz targets over some set of inputs, like for regression/CI tests. Also added a POP3 fuzzer dictionary, seed corpus, and README with examples. --- CMakeLists.txt | 3 + src/fuzzers/CMakeLists.txt | 23 ++++-- src/fuzzers/README | 79 +++++++++++++++++++ src/fuzzers/{fuzz-setup.h => fuzzer-setup.h} | 1 + src/fuzzers/pop3-corpus.zip | Bin 0 -> 210954 bytes src/fuzzers/{pop3.cc => pop3-fuzzer.cc} | 2 +- src/fuzzers/pop3.dict | 21 +++++ src/fuzzers/standalone-driver.cc | 47 +++++++++++ 8 files changed, 168 insertions(+), 8 deletions(-) create mode 100644 src/fuzzers/README rename src/fuzzers/{fuzz-setup.h => fuzzer-setup.h} (96%) create mode 100644 src/fuzzers/pop3-corpus.zip rename src/fuzzers/{pop3.cc => pop3-fuzzer.cc} (98%) create mode 100644 src/fuzzers/pop3.dict create mode 100644 src/fuzzers/standalone-driver.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 8297f588b1..a453be0e8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -481,6 +481,9 @@ message( "\n debugging: ${USE_PERFTOOLS_DEBUG}" "\njemalloc: ${ENABLE_JEMALLOC}" "\n" + "\nFuzz Targets: ${ZEEK_ENABLE_FUZZING}" + "\nFuzz Engine: ${ZEEK_FUZZING_ENGINE}" + "\n" "\n================================================================\n" ) diff --git a/src/fuzzers/CMakeLists.txt b/src/fuzzers/CMakeLists.txt index 6e6d3576b2..1d98ae2057 100644 --- a/src/fuzzers/CMakeLists.txt +++ b/src/fuzzers/CMakeLists.txt @@ -5,13 +5,17 @@ if ( NOT ZEEK_ENABLE_FUZZING ) return() endif () +if ( NOT DEFINED ZEEK_FUZZING_ENGINE AND DEFINED ENV{LIB_FUZZING_ENGINE} ) + set(ZEEK_FUZZING_ENGINE $ENV{LIB_FUZZING_ENGINE} CACHE INTERNAL "" FORCE) +endif () + macro(ADD_FUZZ_TARGET _name) - set(_fuzz_target zeek_fuzzer_${_name}) - set(_fuzz_source ${_name}.cc) + set(_fuzz_target zeek-${_name}-fuzzer) + set(_fuzz_source ${_name}-fuzzer.cc) add_executable(${_fuzz_target} ${_fuzz_source} ${ARGN} $ - $ + $ ${zeek_HEADERS} ${bro_SUBDIR_LIBS} ${bro_PLUGIN_LIBS}) @@ -19,12 +23,17 @@ macro(ADD_FUZZ_TARGET _name) target_link_libraries(${_fuzz_target} ${zeekdeps} ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS}) - # TODO: Use LIB_FUZZING_ENGINE env. var. if it exists - set_target_properties(${_fuzz_target} PROPERTIES LINK_FLAGS - "-fsanitize=fuzzer") + if ( DEFINED ZEEK_FUZZING_ENGINE ) + set_target_properties(${_fuzz_target} PROPERTIES LINK_FLAGS + ${ZEEK_FUZZING_ENGINE}) + else () + target_link_libraries(${_fuzz_target} + $) + endif () endmacro () include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}) -add_library(zeek_fuzzer_objs OBJECT FuzzBuffer.cc) +add_library(zeek_fuzzer_common OBJECT FuzzBuffer.cc) +add_library(zeek_fuzzer_standalone OBJECT standalone-driver.cc) add_fuzz_target(pop3) diff --git a/src/fuzzers/README b/src/fuzzers/README new file mode 100644 index 0000000000..e771b52010 --- /dev/null +++ b/src/fuzzers/README @@ -0,0 +1,79 @@ +Fuzz Testing +============ + +This directory contains fuzzing targets for various Zeek components. The +primary way to use these directly would be with a fuzzing engine such as +libFuzzer: https://llvm.org/docs/LibFuzzer.html + +Example Build: Initial Fuzzing and Seed Corpus +---------------------------------------------- + +First configure and build for fuzzing (with libFuzzer) and code coverage:: + + $ LIB_FUZZING_ENGINE="-fsanitize=fuzzer" CC=clang CXX=clang++ \ + CFLAGS="-fprofile-instr-generate -fcoverage-mapping" \ + CXXFLAGS="-fprofile-instr-generate -fcoverage-mapping" \ + ./configure --build-type=RelWithDebInfo --build-dir=./build-fuzz-cov \ + --sanitizers=fuzzer-no-link --enable-fuzzing + + $ cd build-fuzz-cov && make -j $(nproc) + +Now start fuzzing to generate an initial corpus (this uses the POP3 fuzzer as +an example):: + + $ mkdir corpus && ./src/fuzzers/zeek-pop3-fuzzer corpus \ + -dict=../src/fuzzers/pop3.dict -max_total_time=300 -fork=$(($(nproc) - 1)) + +You can set options, like the runtime and parallelism level, to taste. For +other fuzz targets, you'd also want to use a different dictionary or omit +entirely. + +To minimize the size of the corpus:: + + $ mkdir min-corpus && ./src/fuzzers/zeek-pop3-fuzzer -merge=1 min-corpus corpus + +To check the code coverage of the corpus:: + + $ ./src/fuzzers/zeek-pop3-fuzzer min-corpus/* + + $ llvm-profdata merge -sparse default.profraw -o zeek.profdata && \ + llvm-cov report ./src/fuzzers/zeek-pop3-fuzzer -instr-profile=zeek.profdata \ + ../src/analyzer/protocol/pop3/ + +If the code coverage isn't satisfying, there may be something wrong with +the fuzzer, it may need a better dictionary, or it may need to fuzz for longer. + +The corpus can be added to revision control for use in regression testing and +as seed for OSS-Fuzz (check first that the zip file is a size that's sane to +commit):: + + zip -j ../src/fuzzers/pop3-corpus.zip min-corpus/* + +Example Build: Run Standalone Fuzz Targets +------------------------------------------ + +Fuzz targets can still be run without a fuzzing engine driving them. In +standalone mode, they'll process all input files provided as arguments +(e.g. useful for regression testing). + +First configure and build:: + + $ CC=clang CXX=clang++ \ + ./configure --build-type=debug --build-dir=./build-fuzz-check \ + --sanitizers=address,fuzzer-no-link --enable-fuzzing + + $ cd build-fuzz-cov && make -j $(nproc) + +Get a set of inputs to process (we're using the POP3 fuzzer/corpus as example):: + + $ mkdir corpus && ( cd corpus && unzip ../../src/fuzzers/pop3-corpus.zip ) + +Now run the standalone fuzzer on the input corpus:: + + $ ./src/fuzzers/zeek-pop3-fuzzer corpus/* + +Note that you can also configure this build for coverage reports to verify the +code coverage (see the CFLAGS/CXXFLAGS from the first "Initial Fuzzing" +section). There's also the following ASan option which may need to be used:: + + $ export ASAN_OPTIONS=detect_odr_violation=0 diff --git a/src/fuzzers/fuzz-setup.h b/src/fuzzers/fuzzer-setup.h similarity index 96% rename from src/fuzzers/fuzz-setup.h rename to src/fuzzers/fuzzer-setup.h index 1ad8faff6a..a66d2a8448 100644 --- a/src/fuzzers/fuzz-setup.h +++ b/src/fuzzers/fuzzer-setup.h @@ -16,6 +16,7 @@ extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) options.script_options_to_set.emplace_back("Site::local_nets={10.0.0.0/8}"); options.script_options_to_set.emplace_back("Log::default_writer=Log::WRITER_NONE"); options.deterministic_mode = true; + options.ignore_checksums = true; options.abort_on_scripting_errors = true; if ( zeek::setup(*argc, *argv, &options).code ) diff --git a/src/fuzzers/pop3-corpus.zip b/src/fuzzers/pop3-corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..5ce7592c9634b8094a70eb35f6b90d778ec6705f GIT binary patch literal 210954 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

bW-;P27g!6(S%Kc2h=f5@_pU?Yj{izkr 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 +#include +#include +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv); + +int main(int argc, char** argv) + { + using namespace std::chrono; + auto agg_start = high_resolution_clock::now(); + auto num_inputs = argc - 1; + printf("Standalone fuzzer processing %d inputs\n", num_inputs); + + LLVMFuzzerInitialize(&argc, &argv); + + for ( auto i = 0; i < num_inputs; ++i ) + { + auto input_file_name = argv[i + 1]; + printf(" %s:", input_file_name); + + auto f = fopen(input_file_name, "r"); + assert(f); + + fseek(f, 0, SEEK_END); + auto input_length = ftell(f); + fseek(f, 0, SEEK_SET); + + auto input_buffer = std::make_unique(input_length); + auto bytes_read = fread(input_buffer.get(), 1, input_length, f); + assert(bytes_read == input_length); + + auto start = high_resolution_clock::now(); + LLVMFuzzerTestOneInput(input_buffer.get(), input_length); + auto stop = high_resolution_clock::now(); + auto dt = duration(stop - start).count(); + + printf(" %6zu bytes, %f seconds\n", input_length, dt); + fclose(f); + } + + auto agg_stop = high_resolution_clock::now(); + auto agg_dt = duration(agg_stop - agg_start).count(); + printf("Processed %d inputs in %fs\n", num_inputs, agg_dt); +} From 6721685202d1ce6af116a201643d37848a5e9f7f Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Fri, 24 Apr 2020 11:59:10 -0700 Subject: [PATCH 03/17] Change --enable-fuzzing to --enable-fuzzers Since it controls whether to build the fuzzer targets, not whether those fuzzer targets actually use a fuzzing engine. --- CMakeLists.txt | 2 +- configure | 6 +++--- src/fuzzers/CMakeLists.txt | 2 +- src/fuzzers/README | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a453be0e8e..09e9d835da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -481,7 +481,7 @@ message( "\n debugging: ${USE_PERFTOOLS_DEBUG}" "\njemalloc: ${ENABLE_JEMALLOC}" "\n" - "\nFuzz Targets: ${ZEEK_ENABLE_FUZZING}" + "\nFuzz Targets: ${ZEEK_ENABLE_FUZZERS}" "\nFuzz Engine: ${ZEEK_FUZZING_ENGINE}" "\n" "\n================================================================\n" diff --git a/configure b/configure index a54e1248d2..1fd036f169 100755 --- a/configure +++ b/configure @@ -47,7 +47,7 @@ Usage: $0 [OPTION]... [VAR=VALUE]... Optional Features: --enable-debug compile in debugging mode (like --build-type=Debug) --enable-coverage compile with code coverage support (implies debugging mode) - --enable-fuzzing build fuzzing targets + --enable-fuzzers build fuzzer targets --enable-mobile-ipv6 analyze mobile IPv6 features defined by RFC 6275 --enable-perftools enable use of Google perftools (use tcmalloc) --enable-perftools-debug use Google's perftools for debugging @@ -230,8 +230,8 @@ while [ $# -ne 0 ]; do append_cache_entry ENABLE_COVERAGE BOOL true append_cache_entry ENABLE_DEBUG BOOL true ;; - --enable-fuzzing) - append_cache_entry ZEEK_ENABLE_FUZZING BOOL true + --enable-fuzzers) + append_cache_entry ZEEK_ENABLE_FUZZERS BOOL true ;; --enable-debug) append_cache_entry ENABLE_DEBUG BOOL true diff --git a/src/fuzzers/CMakeLists.txt b/src/fuzzers/CMakeLists.txt index 1d98ae2057..d057f156f4 100644 --- a/src/fuzzers/CMakeLists.txt +++ b/src/fuzzers/CMakeLists.txt @@ -1,7 +1,7 @@ ######################################################################## ## Fuzzing targets -if ( NOT ZEEK_ENABLE_FUZZING ) +if ( NOT ZEEK_ENABLE_FUZZERS ) return() endif () diff --git a/src/fuzzers/README b/src/fuzzers/README index e771b52010..5e85435571 100644 --- a/src/fuzzers/README +++ b/src/fuzzers/README @@ -14,7 +14,7 @@ First configure and build for fuzzing (with libFuzzer) and code coverage:: CFLAGS="-fprofile-instr-generate -fcoverage-mapping" \ CXXFLAGS="-fprofile-instr-generate -fcoverage-mapping" \ ./configure --build-type=RelWithDebInfo --build-dir=./build-fuzz-cov \ - --sanitizers=fuzzer-no-link --enable-fuzzing + --sanitizers=fuzzer-no-link --enable-fuzzers $ cd build-fuzz-cov && make -j $(nproc) @@ -60,9 +60,9 @@ First configure and build:: $ CC=clang CXX=clang++ \ ./configure --build-type=debug --build-dir=./build-fuzz-check \ - --sanitizers=address,fuzzer-no-link --enable-fuzzing + --sanitizers=address --enable-fuzzers - $ cd build-fuzz-cov && make -j $(nproc) + $ cd build-fuzz-check && make -j $(nproc) Get a set of inputs to process (we're using the POP3 fuzzer/corpus as example):: From 2ef182076e8152b46192f5711849c734171937b6 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Fri, 24 Apr 2020 14:43:14 -0700 Subject: [PATCH 04/17] Change handling of LIB_FUZZING_ENGINE Should better support OSS-Fuzz, which may set it to either be path to library to link against or linker flag like "-fsanitize=fuzzer" --- src/fuzzers/CMakeLists.txt | 17 ++++++++++++++--- src/fuzzers/README | 9 ++++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/fuzzers/CMakeLists.txt b/src/fuzzers/CMakeLists.txt index d057f156f4..2cb0fbda2d 100644 --- a/src/fuzzers/CMakeLists.txt +++ b/src/fuzzers/CMakeLists.txt @@ -6,7 +6,19 @@ if ( NOT ZEEK_ENABLE_FUZZERS ) endif () if ( NOT DEFINED ZEEK_FUZZING_ENGINE AND DEFINED ENV{LIB_FUZZING_ENGINE} ) - set(ZEEK_FUZZING_ENGINE $ENV{LIB_FUZZING_ENGINE} CACHE INTERNAL "" FORCE) + if ( "$ENV{LIB_FUZZING_ENGINE}" STREQUAL "" ) + # Empty LIB_FUZZING_ENGINE, assume libFuzzer + set(ZEEK_FUZZING_ENGINE "-fsanitize=fuzzer" CACHE INTERNAL "" FORCE) + else () + STRING(SUBSTRING "$ENV{LIB_FUZZING_ENGINE}" 0 1 _first_char) + + if ( "${_first_char}" STREQUAL "-" OR EXISTS "$ENV{LIB_FUZZING_ENGINE}" ) + # Looks like a linker flag or valid file, use it + set(ZEEK_FUZZING_ENGINE "$ENV{LIB_FUZZING_ENGINE}" CACHE INTERNAL "" FORCE) + else () + message(FATAL_ERROR "Invalid LIB_FUZZING_ENGINE: '$ENV{LIB_FUZZING_ENGINE}'") + endif () + endif () endif () macro(ADD_FUZZ_TARGET _name) @@ -24,8 +36,7 @@ macro(ADD_FUZZ_TARGET _name) ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS}) if ( DEFINED ZEEK_FUZZING_ENGINE ) - set_target_properties(${_fuzz_target} PROPERTIES LINK_FLAGS - ${ZEEK_FUZZING_ENGINE}) + target_link_libraries(${_fuzz_target} ${ZEEK_FUZZING_ENGINE}) else () target_link_libraries(${_fuzz_target} $) diff --git a/src/fuzzers/README b/src/fuzzers/README index 5e85435571..3c04319bf1 100644 --- a/src/fuzzers/README +++ b/src/fuzzers/README @@ -10,7 +10,7 @@ Example Build: Initial Fuzzing and Seed Corpus First configure and build for fuzzing (with libFuzzer) and code coverage:: - $ LIB_FUZZING_ENGINE="-fsanitize=fuzzer" CC=clang CXX=clang++ \ + $ LIB_FUZZING_ENGINE="" CC=clang CXX=clang++ \ CFLAGS="-fprofile-instr-generate -fcoverage-mapping" \ CXXFLAGS="-fprofile-instr-generate -fcoverage-mapping" \ ./configure --build-type=RelWithDebInfo --build-dir=./build-fuzz-cov \ @@ -18,6 +18,13 @@ First configure and build for fuzzing (with libFuzzer) and code coverage:: $ cd build-fuzz-cov && make -j $(nproc) +.. note:: + + The default assumption for empty value of ``LIB_FUZZING_ENGINE`` is to use + libFuzzer by linking with ``-fsanitize=fuzzer``, but that environment + variable may be changed to use another flag or direct path to fuzzing engine + library to link against. + Now start fuzzing to generate an initial corpus (this uses the POP3 fuzzer as an example):: From a4244bc72bcdee64184776547d7eb8228ff55b08 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Fri, 24 Apr 2020 14:51:32 -0700 Subject: [PATCH 05/17] Assume libFuzzer when LIB_FUZZING_ENGINE file doesn't exist i.e. environment variable may be set, but not point to existing file --- src/fuzzers/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fuzzers/CMakeLists.txt b/src/fuzzers/CMakeLists.txt index 2cb0fbda2d..b497baf31e 100644 --- a/src/fuzzers/CMakeLists.txt +++ b/src/fuzzers/CMakeLists.txt @@ -16,7 +16,8 @@ if ( NOT DEFINED ZEEK_FUZZING_ENGINE AND DEFINED ENV{LIB_FUZZING_ENGINE} ) # Looks like a linker flag or valid file, use it set(ZEEK_FUZZING_ENGINE "$ENV{LIB_FUZZING_ENGINE}" CACHE INTERNAL "" FORCE) else () - message(FATAL_ERROR "Invalid LIB_FUZZING_ENGINE: '$ENV{LIB_FUZZING_ENGINE}'") + message(WARNING "$ENV{LIB_FUZZING_ENGINE} does not exist, assume libFuzzer") + set(ZEEK_FUZZING_ENGINE "-fsanitize=fuzzer" CACHE INTERNAL "" FORCE) endif () endif () endif () From 98845e89aa5550bb721d0179f54a9d5ddd3a8800 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Fri, 24 Apr 2020 17:53:01 -0700 Subject: [PATCH 06/17] Add OSS-Fuzz Zeek script search path to fuzzers --- src/fuzzers/fuzzer-setup.h | 17 +++++++++++++++++ src/util.cc | 32 ++++++++++++++++++++++++++++++++ src/util.h | 8 ++++++++ src/zeek-setup.cc | 32 -------------------------------- 4 files changed, 57 insertions(+), 32 deletions(-) diff --git a/src/fuzzers/fuzzer-setup.h b/src/fuzzers/fuzzer-setup.h index a66d2a8448..40e577c5a5 100644 --- a/src/fuzzers/fuzzer-setup.h +++ b/src/fuzzers/fuzzer-setup.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "zeek-setup.h" @@ -11,6 +12,22 @@ extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) { + auto zeekpath = getenv("ZEEKPATH"); + + if ( ! zeekpath ) + { + // Set up an expected script search path for use with OSS-Fuzz + auto constexpr oss_fuzz_scripts = "oss-fuzz-zeek-scripts"; + auto fuzzer_path = get_exe_path(*argv[0]); + auto fuzzer_dir = SafeDirname(fuzzer_path).result; + std::string fs = fmt("%s/%s", fuzzer_dir.data(), oss_fuzz_scripts); + auto p = fs.data(); + auto oss_fuzz_zeekpath = fmt(".:%s:%s/policy:%s/site", p, p, p); + + if ( setenv("ZEEKPATH", oss_fuzz_zeekpath, true) == -1 ) + abort(); + } + zeek::Options options; options.scripts_to_load.emplace_back("local.zeek"); options.script_options_to_set.emplace_back("Site::local_nets={10.0.0.0/8}"); diff --git a/src/util.cc b/src/util.cc index 1293c34804..5c0988177b 100644 --- a/src/util.cc +++ b/src/util.cc @@ -1745,6 +1745,38 @@ static string find_file_in_path(const string& filename, const string& path, return string(); } +std::string get_exe_path(const std::string& invocation) + { + if ( invocation.empty() ) + return ""; + + if ( invocation[0] == '/' || invocation[0] == '~' ) + // Absolute path + return invocation; + + if ( invocation.find('/') != std::string::npos ) + { + // Relative path + char cwd[PATH_MAX]; + + if ( ! getcwd(cwd, sizeof(cwd)) ) + { + fprintf(stderr, "failed to get current directory: %s\n", + strerror(errno)); + exit(1); + } + + return std::string(cwd) + "/" + invocation; + } + + auto path = getenv("PATH"); + + if ( ! path ) + return ""; + + return find_file(invocation, path); + } + string find_file(const string& filename, const string& path_set, const string& opt_ext) { diff --git a/src/util.h b/src/util.h index 1353a36cdd..7878a73a46 100644 --- a/src/util.h +++ b/src/util.h @@ -346,6 +346,14 @@ std::string normalize_path(std::string_view path); */ std::string without_bropath_component(std::string_view path); +/** + * Gets the full path used to invoke some executable. + * @param invocation any possible string that may be seen in argv[0], such as + * absolute path, relative path, or name to lookup in PATH. + * @return the absolute path to the executable file + */ +std::string get_exe_path(const std::string& invocation); + /** * Locate a file within a given search path. * @param filename Name of a file to find. diff --git a/src/zeek-setup.cc b/src/zeek-setup.cc index 8cc392e4fb..6d9d825266 100644 --- a/src/zeek-setup.cc +++ b/src/zeek-setup.cc @@ -374,38 +374,6 @@ static std::vector get_script_signature_files() return rval; } -static std::string get_exe_path(const std::string& invocation) - { - if ( invocation.empty() ) - return ""; - - if ( invocation[0] == '/' || invocation[0] == '~' ) - // Absolute path - return invocation; - - if ( invocation.find('/') != std::string::npos ) - { - // Relative path - char cwd[PATH_MAX]; - - if ( ! getcwd(cwd, sizeof(cwd)) ) - { - fprintf(stderr, "failed to get current directory: %s\n", - strerror(errno)); - exit(1); - } - - return std::string(cwd) + "/" + invocation; - } - - auto path = getenv("PATH"); - - if ( ! path ) - return ""; - - return find_file(invocation, path); - } - zeek::SetupResult zeek::setup(int argc, char** argv, zeek::Options* zopts) { ZEEK_LSAN_DISABLE(); From 91eff92335378c47df65f3614bfeadcf8c30c4b7 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Mon, 27 Apr 2020 15:26:44 -0700 Subject: [PATCH 07/17] Exit immediately after running unit tests --- src/zeek-setup.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zeek-setup.cc b/src/zeek-setup.cc index 6d9d825266..8ad4c2aade 100644 --- a/src/zeek-setup.cc +++ b/src/zeek-setup.cc @@ -410,7 +410,7 @@ zeek::SetupResult zeek::setup(int argc, char** argv, zeek::Options* zopts) auto dargs = to_cargs(options.doctest_args); context.applyCommandLine(dargs.size(), dargs.data()); ZEEK_LSAN_ENABLE(); - return {context.run(), std::move(options)}; + exit(context.run()); } auto stem_state = zeek::Supervisor::CreateStem(options.supervisor_mode); From 0623539d80fbcd4b5a327028e0ed12fa63965cfe Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Mon, 27 Apr 2020 15:46:28 -0700 Subject: [PATCH 08/17] Adjust minor fuzzing documentation --- src/fuzzers/FuzzBuffer.h | 30 +++++++++++++++++++++++++++++- src/fuzzers/README | 5 ++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/fuzzers/FuzzBuffer.h b/src/fuzzers/FuzzBuffer.h index b677e2ac5c..0406be181c 100644 --- a/src/fuzzers/FuzzBuffer.h +++ b/src/fuzzers/FuzzBuffer.h @@ -4,19 +4,47 @@ namespace zeek { -struct FuzzBuffer { +/** + * This structure helps chunk/simulate protocol conversions from arbitrary + * input strings (like those produced by fuzzing engines). A fuzzing engine + * passes in some input string, and we chunk it into originator/responder + * messages according to any PKT_MAGIC delimiting bytestrings found in that + * input (originator vs. responder is determined by inspecting low-bit of + * the byte immediately following PKT_MAGIC and then the remaining bytes up + * to the next PKT_MAGIC delimiter are considered to be the next buffer to + * send along to an analyzers Deliver method. + */ +class FuzzBuffer { static constexpr int PKT_MAGIC_LEN = 4; static constexpr unsigned char PKT_MAGIC[PKT_MAGIC_LEN + 1] = "\1PKT"; + /** + * Initialize fuzz buffer. + * @param data pointer to start of fuzzing buffer produced by fuzz engine. + * @param size size of the fuzzing buffer pointed to by *data*. + */ FuzzBuffer(const unsigned char* data, size_t size) : begin(data), end(data + size) { } + /** + * @return whether the fuzz buffer object is valid -- has enough bytes + * to Deliver to an analyzer and starts with a *PKT_MAGIC* bytestring. + */ bool Valid() const; + /** + * Finds the next chunk of data to pass along to an analyzer. + * @param chunk the data chunk to return + * @param len the size of the chunk returned in *chunk* + * @param is_orig whether returned chunk is from originator or responder + * @return a value less than zero if a chunk could not be extracted + */ int Next(const unsigned char** chunk, size_t* len, bool* is_orig); +private: + const unsigned char* begin; const unsigned char* end; }; diff --git a/src/fuzzers/README b/src/fuzzers/README index 3c04319bf1..82db3e0f1d 100644 --- a/src/fuzzers/README +++ b/src/fuzzers/README @@ -13,7 +13,7 @@ First configure and build for fuzzing (with libFuzzer) and code coverage:: $ LIB_FUZZING_ENGINE="" CC=clang CXX=clang++ \ CFLAGS="-fprofile-instr-generate -fcoverage-mapping" \ CXXFLAGS="-fprofile-instr-generate -fcoverage-mapping" \ - ./configure --build-type=RelWithDebInfo --build-dir=./build-fuzz-cov \ + ./configure --build-type=debug --build-dir=./build-fuzz-cov \ --sanitizers=fuzzer-no-link --enable-fuzzers $ cd build-fuzz-cov && make -j $(nproc) @@ -65,8 +65,7 @@ standalone mode, they'll process all input files provided as arguments First configure and build:: - $ CC=clang CXX=clang++ \ - ./configure --build-type=debug --build-dir=./build-fuzz-check \ + $ ./configure --build-type=debug --build-dir=./build-fuzz-check \ --sanitizers=address --enable-fuzzers $ cd build-fuzz-check && make -j $(nproc) From 8e6539b55f826be7a991893f74a40654c5c261a9 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Mon, 27 Apr 2020 15:53:40 -0700 Subject: [PATCH 09/17] Fix compiler warning in standalone fuzzer driver --- src/fuzzers/standalone-driver.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fuzzers/standalone-driver.cc b/src/fuzzers/standalone-driver.cc index 1557b1d055..280a332110 100644 --- a/src/fuzzers/standalone-driver.cc +++ b/src/fuzzers/standalone-driver.cc @@ -30,7 +30,7 @@ int main(int argc, char** argv) auto input_buffer = std::make_unique(input_length); auto bytes_read = fread(input_buffer.get(), 1, input_length, f); - assert(bytes_read == input_length); + assert(bytes_read == static_cast(input_length)); auto start = high_resolution_clock::now(); LLVMFuzzerTestOneInput(input_buffer.get(), input_length); From 2922bf71b64a20317b7ff6bd496873ef09c9e02e Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Mon, 27 Apr 2020 16:25:56 -0700 Subject: [PATCH 10/17] Improve FuzzBuffer chunking Now allocates a new buffer for each chunk to better detect over-reads --- src/fuzzers/FuzzBuffer.cc | 34 ++++++++++++++++++++++------------ src/fuzzers/FuzzBuffer.h | 17 +++++++++++------ src/fuzzers/pop3-fuzzer.cc | 14 ++++---------- 3 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/fuzzers/FuzzBuffer.cc b/src/fuzzers/FuzzBuffer.cc index cc25550983..60ae96ca27 100644 --- a/src/fuzzers/FuzzBuffer.cc +++ b/src/fuzzers/FuzzBuffer.cc @@ -17,31 +17,28 @@ bool zeek::FuzzBuffer::Valid() const return true; } -int zeek::FuzzBuffer::Next(const unsigned char** chunk, size_t* len, bool* is_orig) +std::optional zeek::FuzzBuffer::Next() { if ( begin == end ) - { - *chunk = nullptr; - *len = 0; - return 0; - } + return {}; auto pos = (const unsigned char*)memmem(begin, end - begin, PKT_MAGIC, PKT_MAGIC_LEN); if ( ! pos ) - return -1; + return {}; begin += PKT_MAGIC_LEN; auto remaining = end - begin; if ( remaining < 2 ) - return -2; + return {}; - *is_orig = begin[0] & 0x01; + Chunk rval; + rval.is_orig = begin[0] & 0x01; begin += 1; - *chunk = begin; + auto chunk_begin = begin; auto next = (const unsigned char*)memmem(begin, end - begin, PKT_MAGIC, PKT_MAGIC_LEN); @@ -51,6 +48,19 @@ int zeek::FuzzBuffer::Next(const unsigned char** chunk, size_t* len, bool* is_or else begin = end; - *len = begin - *chunk; - return 0; + rval.size = begin - chunk_begin; + + if ( rval.size ) + { + // The point of allocating a new buffer here is to better detect + // analyzers that may over-read within a chunk -- ASan wouldn't + // complain if that happens to land within the full input buffer + // provided by the fuzzing engine, but will if we allocate a new buffer + // for each chunk. + rval.data = std::make_unique(rval.size); + memcpy(rval.data.get(), chunk_begin, rval.size); + return rval; + } + + return {}; } diff --git a/src/fuzzers/FuzzBuffer.h b/src/fuzzers/FuzzBuffer.h index 0406be181c..81770857e6 100644 --- a/src/fuzzers/FuzzBuffer.h +++ b/src/fuzzers/FuzzBuffer.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include namespace zeek { @@ -15,6 +17,13 @@ namespace zeek { * send along to an analyzers Deliver method. */ class FuzzBuffer { +public: + + struct Chunk { + std::unique_ptr data; + size_t size; + bool is_orig; + }; static constexpr int PKT_MAGIC_LEN = 4; static constexpr unsigned char PKT_MAGIC[PKT_MAGIC_LEN + 1] = "\1PKT"; @@ -35,13 +44,9 @@ class FuzzBuffer { bool Valid() const; /** - * Finds the next chunk of data to pass along to an analyzer. - * @param chunk the data chunk to return - * @param len the size of the chunk returned in *chunk* - * @param is_orig whether returned chunk is from originator or responder - * @return a value less than zero if a chunk could not be extracted + * @return the next chunk to deliver, if one could be extracted */ - int Next(const unsigned char** chunk, size_t* len, bool* is_orig); + std::optional Next(); private: diff --git a/src/fuzzers/pop3-fuzzer.cc b/src/fuzzers/pop3-fuzzer.cc index 89bd1e00b5..394a6f508a 100644 --- a/src/fuzzers/pop3-fuzzer.cc +++ b/src/fuzzers/pop3-fuzzer.cc @@ -53,28 +53,22 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) auto conn = add_connection(); auto a = add_analyzer(conn); - const unsigned char* chunk; - size_t chunk_size; - bool is_orig; - for ( ; ; ) { - auto err = fb.Next(&chunk, &chunk_size, &is_orig); + auto chunk = fb.Next(); - if ( err ) - break; - - if ( chunk_size == 0 ) + if ( ! chunk ) break; try { - a->DeliverStream(chunk_size, chunk, is_orig); + a->DeliverStream(chunk->size, chunk->data.get(), chunk->is_orig); } catch ( const binpac::Exception& e ) { } + chunk = {}; mgr.Drain(); } From 8ec807bd760892475d57d341c7d62df6e16f85a7 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Mon, 27 Apr 2020 20:04:33 -0700 Subject: [PATCH 11/17] Link fuzzers against shared library to reduce executable sizes --- CMakeLists.txt | 4 ++++ src/fuzzers/CMakeLists.txt | 37 +++++++++++++++++++++++++++++-------- src/fuzzers/README | 10 ++++------ 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 09e9d835da..72fe9bcc54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,10 @@ include(cmake/FindClangTidy.cmake) ######################################################################## ## Project/Build Configuration +if ( ZEEK_ENABLE_FUZZERS ) + # Fuzzers use shared lib to save disk space, so need -fPIC on everything + set(CMAKE_POSITION_INDEPENDENT_CODE ON) +endif () if (ENABLE_ZEEK_UNIT_TESTS) enable_testing() diff --git a/src/fuzzers/CMakeLists.txt b/src/fuzzers/CMakeLists.txt index b497baf31e..97a050d265 100644 --- a/src/fuzzers/CMakeLists.txt +++ b/src/fuzzers/CMakeLists.txt @@ -26,14 +26,11 @@ macro(ADD_FUZZ_TARGET _name) set(_fuzz_target zeek-${_name}-fuzzer) set(_fuzz_source ${_name}-fuzzer.cc) - add_executable(${_fuzz_target} ${_fuzz_source} ${ARGN} - $ - $ - ${zeek_HEADERS} - ${bro_SUBDIR_LIBS} - ${bro_PLUGIN_LIBS}) + add_executable(${_fuzz_target} ${_fuzz_source} ${ARGN}) - target_link_libraries(${_fuzz_target} ${zeekdeps} + target_link_libraries(${_fuzz_target} + zeek_fuzzer_shared + ${BIND_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS}) if ( DEFINED ZEEK_FUZZING_ENGINE ) @@ -45,7 +42,31 @@ macro(ADD_FUZZ_TARGET _name) endmacro () include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}) -add_library(zeek_fuzzer_common OBJECT FuzzBuffer.cc) + add_library(zeek_fuzzer_standalone OBJECT standalone-driver.cc) +add_library(zeek_fuzzer_shared SHARED + $ + ${bro_SUBDIR_LIBS} + ${bro_PLUGIN_LIBS} + FuzzBuffer.cc +) + +set(zeek_fuzzer_shared_deps) + +foreach(_dep ${zeekdeps} ) + # The bind library is handled a bit hack-ishly since it defaults to + # linking it as static library by default on Linux, but at least + # on one common distro, that static library wasn't compiled with -fPIC + # and so not usable in the shared library we're trying to build. + # So instead, the fuzzer executable, not the shared lib, links it. + if ( NOT "${_dep}" STREQUAL "${BIND_LIBRARY}" ) + set(zeek_fuzzer_shared_deps ${zeek_fuzzer_shared_deps} ${_dep}) + endif () +endforeach () + +target_link_libraries(zeek_fuzzer_shared + ${zeek_fuzzer_shared_deps} + ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS}) + add_fuzz_target(pop3) diff --git a/src/fuzzers/README b/src/fuzzers/README index 82db3e0f1d..bf8959a1ac 100644 --- a/src/fuzzers/README +++ b/src/fuzzers/README @@ -11,10 +11,8 @@ Example Build: Initial Fuzzing and Seed Corpus First configure and build for fuzzing (with libFuzzer) and code coverage:: $ LIB_FUZZING_ENGINE="" CC=clang CXX=clang++ \ - CFLAGS="-fprofile-instr-generate -fcoverage-mapping" \ - CXXFLAGS="-fprofile-instr-generate -fcoverage-mapping" \ ./configure --build-type=debug --build-dir=./build-fuzz-cov \ - --sanitizers=fuzzer-no-link --enable-fuzzers + --sanitizers=fuzzer-no-link --enable-fuzzers --enable-coverage $ cd build-fuzz-cov && make -j $(nproc) @@ -43,9 +41,9 @@ To check the code coverage of the corpus:: $ ./src/fuzzers/zeek-pop3-fuzzer min-corpus/* - $ llvm-profdata merge -sparse default.profraw -o zeek.profdata && \ - llvm-cov report ./src/fuzzers/zeek-pop3-fuzzer -instr-profile=zeek.profdata \ - ../src/analyzer/protocol/pop3/ + $ llvm-cov gcov $(find . -name POP3.cc.gcda) | grep -A1 POP3.cc + + # Annotated source file is now output to POP3.cc.gcov If the code coverage isn't satisfying, there may be something wrong with the fuzzer, it may need a better dictionary, or it may need to fuzz for longer. From f1d21fa48950df100e41180f934821a89d8952e5 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Tue, 28 Apr 2020 14:32:37 -0700 Subject: [PATCH 12/17] Update fuzzing README with OSS-Fuzz integration notes --- src/fuzzers/README | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/fuzzers/README b/src/fuzzers/README index bf8959a1ac..1d6857ff1b 100644 --- a/src/fuzzers/README +++ b/src/fuzzers/README @@ -81,3 +81,22 @@ code coverage (see the CFLAGS/CXXFLAGS from the first "Initial Fuzzing" section). There's also the following ASan option which may need to be used:: $ export ASAN_OPTIONS=detect_odr_violation=0 + +OSS-Fuzz Integration +-------------------- + +The OSS-Fuzz integration is all contained in the external OSS-Fuzz repo's +Zeek project: https://github.com/google/oss-fuzz + +There's not much to it other than Dockerfile and build script, but a couple +conventions to follow to support the OSS-Fuzz configuration: + +* Fuzz target names are all like ``zeek-*-fuzzer``. The OSS-Fuzz build + scripts expects that and won't pick up any fuzzer that are named differently. + +* Fuzzers should expect to have to load Zeek scripts from a directory named + ``oss-fuzz-zeek-scripts`` that lives next to the fuzzer executable. When + running fuzzers locally, the usual way of setting ``ZEEKPATH`` from the build + directory does still work, but fuzzers should additionally augment + ``ZEEKPATH`` with that special OSS-Fuzz scripts directory so they'll be able + to run in that environment. From db5248ad852e745fd3ff2b6f345a16c686085ad0 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Tue, 28 Apr 2020 15:28:45 -0700 Subject: [PATCH 13/17] Test fuzzers against seed corpus under CI ASan build --- .cirrus.yml | 3 ++- ci/test-fuzzers.sh | 40 ++++++++++++++++++++++++++++++ ci/ubuntu-18.04/Dockerfile | 1 + src/analyzer/protocol/pop3/POP3.cc | 2 +- src/fuzzers/FuzzBuffer.cc | 2 +- src/fuzzers/standalone-driver.cc | 6 ++++- 6 files changed, 50 insertions(+), 4 deletions(-) create mode 100755 ci/test-fuzzers.sh diff --git a/.cirrus.yml b/.cirrus.yml index f501f0bc4d..437388adf6 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -4,7 +4,7 @@ btest_retries: &BTEST_RETRIES 2 memory: &MEMORY 6GB config: &CONFIG --build-type=release --enable-cpp-tests -memcheck_config: &MEMCHECK_CONFIG --build-type=debug --enable-cpp-tests --sanitizers=address +memcheck_config: &MEMCHECK_CONFIG --build-type=debug --enable-cpp-tests --sanitizers=address --enable-fuzzers resources_template: &RESOURCES_TEMPLATE cpu: *CPUS @@ -133,5 +133,6 @@ memcheck_task: # AddressSanitizer uses a lot more memory than a typical config. memory: 16GB << : *CI_TEMPLATE + test_fuzzers_script: ./ci/test-fuzzers.sh env: ZEEK_CI_CONFIGURE_FLAGS: *MEMCHECK_CONFIG diff --git a/ci/test-fuzzers.sh b/ci/test-fuzzers.sh new file mode 100755 index 0000000000..c3ae93d469 --- /dev/null +++ b/ci/test-fuzzers.sh @@ -0,0 +1,40 @@ +#! /usr/bin/env bash + +result=0 + +echo "Testing fuzzers against their seed corpus" +echo "-----------------------------------------" + +cd build || result=1 +. ./zeek-path-dev.sh + +fuzzers=$(find ./src/fuzzers -name 'zeek-*-fuzzer') + +for fuzzer_path in ${fuzzers}; do + fuzzer_exe=$(basename ${fuzzer_path}) + fuzzer_name=$(echo ${fuzzer_exe} | sed 's/zeek-\(.*\)-fuzzer/\1/g') + corpus="../src/fuzzers/${fuzzer_name}-corpus.zip" + + if [[ -e ${corpus} ]]; then + echo "Fuzzer: ${fuzzer_exe} ${corpus}" + ( rm -rf corpus && mkdir corpus ) || result=1 + ( cd corpus && unzip ../${corpus} >/dev/null ) || result=1 + ${fuzzer_path} corpus/* >${fuzzer_exe}.out 2>${fuzzer_exe}.err + + if [[ $? -eq 0 ]]; then + tail -n 1 ${fuzzer_exe}.out + else + result=1 + cat ${fuzzer_exe}.out + echo " FAILED" + cat ${fuzzer_exe}.err + fi + else + echo "Skipping Fuzzer (no corpus): ${fuzzer_exe}" + fi + + echo "-----------------------------------------" +done + + +exit ${result} diff --git a/ci/ubuntu-18.04/Dockerfile b/ci/ubuntu-18.04/Dockerfile index 64b84692d2..272929749e 100644 --- a/ci/ubuntu-18.04/Dockerfile +++ b/ci/ubuntu-18.04/Dockerfile @@ -20,6 +20,7 @@ RUN apt-get update && apt-get -y install \ sqlite3 \ curl \ wget \ + unzip \ && rm -rf /var/lib/apt/lists/* # Many distros adhere to PEP 394's recommendation for `python` = `python2` so diff --git a/src/analyzer/protocol/pop3/POP3.cc b/src/analyzer/protocol/pop3/POP3.cc index c6753c01da..af91bc4e9f 100644 --- a/src/analyzer/protocol/pop3/POP3.cc +++ b/src/analyzer/protocol/pop3/POP3.cc @@ -864,7 +864,7 @@ int POP3_Analyzer::ParseCmd(std::string cmd) if ( cmd.size() == 0 ) return -1; - for ( int code = POP3_CMD_OK; code <= POP3_CMD_END; ++code ) + for ( int code = POP3_CMD_OK; code < POP3_CMD_END; ++code ) { char c = cmd.c_str()[0]; if ( c == '+' || c == '-' ) diff --git a/src/fuzzers/FuzzBuffer.cc b/src/fuzzers/FuzzBuffer.cc index 60ae96ca27..866b63bb20 100644 --- a/src/fuzzers/FuzzBuffer.cc +++ b/src/fuzzers/FuzzBuffer.cc @@ -59,7 +59,7 @@ std::optional zeek::FuzzBuffer::Next() // for each chunk. rval.data = std::make_unique(rval.size); memcpy(rval.data.get(), chunk_begin, rval.size); - return rval; + return {std::move(rval)}; } return {}; diff --git a/src/fuzzers/standalone-driver.cc b/src/fuzzers/standalone-driver.cc index 280a332110..e603d73735 100644 --- a/src/fuzzers/standalone-driver.cc +++ b/src/fuzzers/standalone-driver.cc @@ -20,6 +20,10 @@ int main(int argc, char** argv) { auto input_file_name = argv[i + 1]; printf(" %s:", input_file_name); + // If ASan ends up aborting, the previous stdout output may not + // be flushed, so make sure to that and make it easier to see + // what input caused the crash. + fflush(stdout); auto f = fopen(input_file_name, "r"); assert(f); @@ -44,4 +48,4 @@ int main(int argc, char** argv) auto agg_stop = high_resolution_clock::now(); auto agg_dt = duration(agg_stop - agg_start).count(); printf("Processed %d inputs in %fs\n", num_inputs, agg_dt); -} + } From 787ebd369e7fa7206bfc73702c8e9a151240d2f9 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Thu, 30 Apr 2020 13:43:57 -0700 Subject: [PATCH 14/17] Improve standalone fuzzer driver error messages --- src/fuzzers/standalone-driver.cc | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/fuzzers/standalone-driver.cc b/src/fuzzers/standalone-driver.cc index e603d73735..840b8d8e52 100644 --- a/src/fuzzers/standalone-driver.cc +++ b/src/fuzzers/standalone-driver.cc @@ -1,6 +1,7 @@ #include #include -#include +#include +#include #include #include @@ -26,7 +27,12 @@ int main(int argc, char** argv) fflush(stdout); auto f = fopen(input_file_name, "r"); - assert(f); + + if ( ! f ) + { + printf(" failed to open file: %s\n", strerror(errno)); + abort(); + } fseek(f, 0, SEEK_END); auto input_length = ftell(f); @@ -34,7 +40,13 @@ int main(int argc, char** argv) auto input_buffer = std::make_unique(input_length); auto bytes_read = fread(input_buffer.get(), 1, input_length, f); - assert(bytes_read == static_cast(input_length)); + + if ( bytes_read != static_cast(input_length) ) + { + printf(" failed to read full file: %zu/%ld\n", + bytes_read, input_length); + abort(); + } auto start = high_resolution_clock::now(); LLVMFuzzerTestOneInput(input_buffer.get(), input_length); From d449682ff06db27e25fde13d3c297baabcbe08e9 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Fri, 1 May 2020 13:47:36 -0700 Subject: [PATCH 15/17] Add missing include to standalone fuzzer driver --- src/fuzzers/standalone-driver.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fuzzers/standalone-driver.cc b/src/fuzzers/standalone-driver.cc index 840b8d8e52..69ca7202cf 100644 --- a/src/fuzzers/standalone-driver.cc +++ b/src/fuzzers/standalone-driver.cc @@ -1,4 +1,5 @@ #include +#include #include #include #include From 5a2d25c9547126400de014a775fd96c56d92cc0b Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Mon, 4 May 2020 17:29:21 -0700 Subject: [PATCH 16/17] Set terminating flag during fuzzer cleanup --- src/fuzzers/fuzzer-setup.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/fuzzers/fuzzer-setup.h b/src/fuzzers/fuzzer-setup.h index 40e577c5a5..c8714f0ac2 100644 --- a/src/fuzzers/fuzzer-setup.h +++ b/src/fuzzers/fuzzer-setup.h @@ -46,6 +46,7 @@ namespace zeek { void fuzz_cleanup_one_input() { + terminating = true; broker_mgr->ClearStores(); file_mgr->Terminate(); timer_mgr->Expire(); @@ -54,6 +55,7 @@ void fuzz_cleanup_one_input() sessions->Drain(); mgr.Drain(); sessions->Clear(); + terminating = false; } } // namespace zeek From 2d0b8c0b8ef9f71ca880660f7b7b23ab113f8bcb Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Mon, 4 May 2020 17:37:11 -0700 Subject: [PATCH 17/17] Use zeek::detail namespace for fuzzer utils --- src/fuzzers/FuzzBuffer.cc | 4 ++-- src/fuzzers/FuzzBuffer.h | 4 ++-- src/fuzzers/fuzzer-setup.h | 8 ++++---- src/fuzzers/pop3-fuzzer.cc | 4 ++-- src/main.cc | 4 ++-- src/zeek-setup.cc | 5 +++-- src/zeek-setup.h | 4 ++-- 7 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/fuzzers/FuzzBuffer.cc b/src/fuzzers/FuzzBuffer.cc index 866b63bb20..ebae1a1db1 100644 --- a/src/fuzzers/FuzzBuffer.cc +++ b/src/fuzzers/FuzzBuffer.cc @@ -6,7 +6,7 @@ #include "FuzzBuffer.h" -bool zeek::FuzzBuffer::Valid() const +bool zeek::detail::FuzzBuffer::Valid() const { if ( end - begin < PKT_MAGIC_LEN + 2 ) return false; @@ -17,7 +17,7 @@ bool zeek::FuzzBuffer::Valid() const return true; } -std::optional zeek::FuzzBuffer::Next() +std::optional zeek::detail::FuzzBuffer::Next() { if ( begin == end ) return {}; diff --git a/src/fuzzers/FuzzBuffer.h b/src/fuzzers/FuzzBuffer.h index 81770857e6..1b9cdb58a3 100644 --- a/src/fuzzers/FuzzBuffer.h +++ b/src/fuzzers/FuzzBuffer.h @@ -4,7 +4,7 @@ #include #include -namespace zeek { +namespace zeek { namespace detail { /** * This structure helps chunk/simulate protocol conversions from arbitrary @@ -54,4 +54,4 @@ private: const unsigned char* end; }; -} // namespace zeek +}} // namespace zeek::detail diff --git a/src/fuzzers/fuzzer-setup.h b/src/fuzzers/fuzzer-setup.h index c8714f0ac2..e692ddd3c4 100644 --- a/src/fuzzers/fuzzer-setup.h +++ b/src/fuzzers/fuzzer-setup.h @@ -36,15 +36,15 @@ extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) options.ignore_checksums = true; options.abort_on_scripting_errors = true; - if ( zeek::setup(*argc, *argv, &options).code ) + if ( zeek::detail::setup(*argc, *argv, &options).code ) abort(); return 0; } -namespace zeek { +namespace zeek { namespace detail { -void fuzz_cleanup_one_input() +void fuzzer_cleanup_one_input() { terminating = true; broker_mgr->ClearStores(); @@ -58,4 +58,4 @@ void fuzz_cleanup_one_input() terminating = false; } -} // namespace zeek +}} // namespace zeek::detail diff --git a/src/fuzzers/pop3-fuzzer.cc b/src/fuzzers/pop3-fuzzer.cc index 394a6f508a..5cc4776aad 100644 --- a/src/fuzzers/pop3-fuzzer.cc +++ b/src/fuzzers/pop3-fuzzer.cc @@ -45,7 +45,7 @@ static analyzer::Analyzer* add_analyzer(Connection* conn) extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - zeek::FuzzBuffer fb{data, size}; + zeek::detail::FuzzBuffer fb{data, size}; if ( ! fb.Valid() ) return 0; @@ -72,6 +72,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) mgr.Drain(); } - zeek::fuzz_cleanup_one_input(); + zeek::detail::fuzzer_cleanup_one_input(); return 0; } diff --git a/src/main.cc b/src/main.cc index 7d7e876f41..b107dd213c 100644 --- a/src/main.cc +++ b/src/main.cc @@ -10,7 +10,7 @@ int main(int argc, char** argv) { auto time_start = current_time(true); - auto setup_result = zeek::setup(argc, argv); + auto setup_result = zeek::detail::setup(argc, argv); if ( setup_result.code ) return setup_result.code; @@ -78,5 +78,5 @@ int main(int argc, char** argv) } } - return zeek::cleanup(do_net_run); + return zeek::detail::cleanup(do_net_run); } diff --git a/src/zeek-setup.cc b/src/zeek-setup.cc index 25251d4555..34ff4a7609 100644 --- a/src/zeek-setup.cc +++ b/src/zeek-setup.cc @@ -374,7 +374,8 @@ static std::vector get_script_signature_files() return rval; } -zeek::SetupResult zeek::setup(int argc, char** argv, zeek::Options* zopts) +zeek::detail::SetupResult zeek::detail::setup(int argc, char** argv, + zeek::Options* zopts) { ZEEK_LSAN_DISABLE(); std::set_new_handler(bro_new_handler); @@ -866,7 +867,7 @@ zeek::SetupResult zeek::setup(int argc, char** argv, zeek::Options* zopts) return {0, std::move(options)}; } -int zeek::cleanup(bool did_net_run) +int zeek::detail::cleanup(bool did_net_run) { if ( did_net_run ) done_with_network(); diff --git a/src/zeek-setup.h b/src/zeek-setup.h index 303ded4abd..22cb85fde7 100644 --- a/src/zeek-setup.h +++ b/src/zeek-setup.h @@ -4,7 +4,7 @@ #include "Options.h" -namespace zeek { +namespace zeek { namespace detail { struct SetupResult { int code = 0; @@ -28,4 +28,4 @@ SetupResult setup(int argc, char** argv, zeek::Options* options = nullptr); */ int cleanup(bool did_net_run); -} +}} // namespace zeek::detail