From 5d781e54bdf1e0156eb1eb382f5c4ad3ed4188ad Mon Sep 17 00:00:00 2001 From: Arne Welzel Date: Thu, 26 Oct 2023 12:54:11 +0200 Subject: [PATCH] generic-analyzer-fuzzer: Support NextPacket() fuzzing A number of analyzers that we've been fuzzing with the generic-analyzer-fuzzer setup do not implement DeliverStream() and instead only work with DeliverPacket() (ntp, syslog, sip, radius, ...). Calling DeliverStream() on those is pretty much a noop and fuzzing not effective. This change adds support to fuzz DeliverPacket(). Whether to use packet or stream fuzzing is configured through a define via CMake. This is still a bit limited in that for analyzers that support both, DeliverPacket() and DeliverStream(), only one code path is fuzzed. Closed #3398 --- src/fuzzers/CMakeLists.txt | 23 ++- src/fuzzers/generic-analyzer-fuzzer.cc | 225 +++++++++++++++++++------ 2 files changed, 186 insertions(+), 62 deletions(-) diff --git a/src/fuzzers/CMakeLists.txt b/src/fuzzers/CMakeLists.txt index a908ae23a3..b9bb0b03a5 100644 --- a/src/fuzzers/CMakeLists.txt +++ b/src/fuzzers/CMakeLists.txt @@ -55,10 +55,18 @@ macro (ADD_FUZZ_TARGET _name) endmacro () macro (ADD_GENERIC_ANALYZER_FUZZ_TARGET _name) + set(_transport "tcp") + set(extra_args "${ARGN}") + list(LENGTH extra_args extra_args_n) + if (${extra_args_n} GREATER 0) + list(GET extra_args 0 _transport) + endif () + set(_fuzz_target zeek-${_name}-fuzzer) set(_fuzz_source generic-analyzer-fuzzer.cc) setup_fuzz_target(${_fuzz_target} ${_fuzz_source}) target_compile_definitions(${_fuzz_target} PUBLIC ZEEK_FUZZ_ANALYZER=${_name}) + target_compile_definitions(${_fuzz_target} PUBLIC ZEEK_FUZZ_ANALYZER_TRANSPORT=${_transport}) endmacro () include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}) @@ -94,27 +102,28 @@ add_generic_analyzer_fuzz_target(pop3) add_generic_analyzer_fuzz_target(smtp) add_generic_analyzer_fuzz_target(dce_rpc) -add_generic_analyzer_fuzz_target(dhcp) +add_generic_analyzer_fuzz_target(dhcp udp) add_generic_analyzer_fuzz_target(dnp3_tcp) add_generic_analyzer_fuzz_target(irc) add_generic_analyzer_fuzz_target(modbus) add_generic_analyzer_fuzz_target(mqtt) add_generic_analyzer_fuzz_target(mysql) add_generic_analyzer_fuzz_target(ncp) -add_generic_analyzer_fuzz_target(ntp) -add_generic_analyzer_fuzz_target(radius) +add_generic_analyzer_fuzz_target(ntp udp) +add_generic_analyzer_fuzz_target(radius udp) add_generic_analyzer_fuzz_target(rdp) -add_generic_analyzer_fuzz_target(rdpeudp) +add_generic_analyzer_fuzz_target(rdpeudp udp) add_generic_analyzer_fuzz_target(rfb) +# The rpc based analyzer work with udp and tcp. add_generic_analyzer_fuzz_target(mount) # rpc add_generic_analyzer_fuzz_target(nfs) # rpc add_generic_analyzer_fuzz_target(portmapper) # rpc -add_generic_analyzer_fuzz_target(sip) +add_generic_analyzer_fuzz_target(sip udp) add_generic_analyzer_fuzz_target(smb) -add_generic_analyzer_fuzz_target(snmp) +add_generic_analyzer_fuzz_target(snmp udp) add_generic_analyzer_fuzz_target(ssh) add_generic_analyzer_fuzz_target(ssl) -add_generic_analyzer_fuzz_target(syslog) +add_generic_analyzer_fuzz_target(syslog udp) # add_generic_analyzer_fuzz_target(finger) # no pcap files # add_generic_analyzer_fuzz_target(gssapi) # only samples are embedded in smb diff --git a/src/fuzzers/generic-analyzer-fuzzer.cc b/src/fuzzers/generic-analyzer-fuzzer.cc index 4526f2edad..d427a8508e 100644 --- a/src/fuzzers/generic-analyzer-fuzzer.cc +++ b/src/fuzzers/generic-analyzer-fuzzer.cc @@ -1,3 +1,12 @@ +// Generic protocol analyzer fuzzer. +// +// Expects ZEEK_FUZZ_ANALYZER and ZEEK_FUZZ_ANALYZER_TRANSPORT to be set. +// +// ZEEK_FUZZER_ANALYZER_TRANSPORT can be "tcp" or "udp" and determines if +// fuzzing happens via NextStream() or NextPacket(). +// +// Fuzzing both codepaths at the same time isn't currently supported. +// Further note that TCP analyzers may use DeliverPacket() as well. #include #include "zeek/Conn.h" @@ -8,51 +17,179 @@ #include "zeek/analyzer/protocol/tcp/TCP.h" #include "zeek/fuzzers/FuzzBuffer.h" #include "zeek/fuzzers/fuzzer-setup.h" +#include "zeek/packet_analysis/protocol/ip/SessionAdapter.h" #include "zeek/packet_analysis/protocol/tcp/TCPSessionAdapter.h" +#include "zeek/packet_analysis/protocol/udp/UDPSessionAdapter.h" #include "zeek/session/Manager.h" // Simple macros for converting a compiler define into a string. #define VAL(str) #str #define TOSTRING(str) VAL(str) -static zeek::Connection* add_connection() - { - static constexpr double network_time_start = 1439471031; - zeek::run_state::detail::update_network_time(network_time_start); +static const char* FUZZ_ANALYZER_NAME = TOSTRING(ZEEK_FUZZ_ANALYZER); +static const char* FUZZ_ANALYZER_TRANSPORT = TOSTRING(ZEEK_FUZZ_ANALYZER_TRANSPORT); - zeek::Packet p; - zeek::ConnTuple conn_id; - conn_id.src_addr = zeek::IPAddr("1.2.3.4"); - conn_id.dst_addr = zeek::IPAddr("5.6.7.8"); - conn_id.src_port = htons(23132); - conn_id.dst_port = htons(80); - conn_id.is_one_way = false; - conn_id.proto = TRANSPORT_TCP; - zeek::detail::ConnKey key(conn_id); - zeek::Connection* conn = new zeek::Connection(key, network_time_start, &conn_id, 1, &p); - conn->SetTransport(TRANSPORT_TCP); - zeek::session_mgr->Insert(conn); - return conn; - } - -static std::pair -add_analyzer(zeek::Connection* conn, zeek::Tag tag) +class Fuzzer { - auto* tcp = new zeek::packet_analysis::TCP::TCPSessionAdapter(conn); - auto* pia = new zeek::analyzer::pia::PIA_TCP(conn); - auto a = zeek::analyzer_mgr->InstantiateAnalyzer(tag, conn); - if ( ! a ) +public: + Fuzzer(TransportProto proto, const zeek::Tag& analyzer_tag) + : proto{proto}, analyzer_tag{analyzer_tag} { - fprintf(stderr, "Unknown or unsupported analyzer %s found\n", TOSTRING(ZEEK_FUZZ_ANALYZER)); + } + + virtual ~Fuzzer(){}; + + zeek::Connection* AddConnection() + { + static constexpr double network_time_start = 1439471031; + zeek::run_state::detail::update_network_time(network_time_start); + + zeek::Packet p; + zeek::ConnTuple conn_id; + conn_id.src_addr = zeek::IPAddr("1.2.3.4"); + conn_id.dst_addr = zeek::IPAddr("5.6.7.8"); + conn_id.src_port = htons(23132); + conn_id.dst_port = htons(80); + conn_id.is_one_way = false; + conn_id.proto = proto; + zeek::detail::ConnKey key(conn_id); + zeek::Connection* conn = new zeek::Connection(key, network_time_start, &conn_id, 1, &p); + conn->SetTransport(proto); + zeek::session_mgr->Insert(conn); + return conn; + } + + std::tuple + Setup() + { + auto* conn = AddConnection(); + auto* analyzer = zeek::analyzer_mgr->InstantiateAnalyzer(analyzer_tag, conn); + if ( ! analyzer ) + { + fprintf(stderr, "Unknown or unsupported analyzer %s\n", + analyzer_tag.AsString().c_str()); + abort(); + } + + auto* adapter = BuildAnalyzerTree(conn, analyzer); + + return {analyzer, adapter, conn}; + } + + void Process(zeek::detail::FuzzBuffer& fb) + { + auto [analyzer, adapter, conn] = Setup(); + + if ( new_connection ) + conn->Event(new_connection, nullptr); + + for ( ;; ) + { + auto chunk = fb.Next(); + + if ( ! chunk ) + break; + + try + { + NextChunk(analyzer, *chunk); + } + catch ( const binpac::Exception& e ) + { + } + + chunk = {}; // Release buffer before draining events. + zeek::event_mgr.Drain(); + + // Has the analyzer been disabled during event processing? + if ( ! adapter->HasChildAnalyzer(analyzer_tag) ) + break; + } + } + + // Hook methods to be implemented by specific fuzzers. + virtual zeek::packet_analysis::IP::SessionAdapter* + BuildAnalyzerTree(zeek::Connection* conn, zeek::analyzer::Analyzer* analyzer) = 0; + virtual void NextChunk(zeek::analyzer::Analyzer* analyzer, + zeek::detail::FuzzBuffer::Chunk& chunk) = 0; + + void Cleanup() { zeek::detail::fuzzer_cleanup_one_input(); } + + // Create a Fuzzer given FUZZ_ANALYZER_NAME and FUZZ_ANALYZER_TRANSPORT globals. + static std::unique_ptr Create(); + +protected: + TransportProto proto; + zeek::Tag analyzer_tag; + }; + +class TCPFuzzer : public Fuzzer + { +public: + TCPFuzzer(const zeek::Tag& analyzer_tag) : Fuzzer(TRANSPORT_TCP, analyzer_tag) { } + + zeek::packet_analysis::IP::SessionAdapter* + BuildAnalyzerTree(zeek::Connection* conn, zeek::analyzer::Analyzer* analyzer) override + { + auto* tcp = new zeek::packet_analysis::TCP::TCPSessionAdapter(conn); + auto* pia = new zeek::analyzer::pia::PIA_TCP(conn); + tcp->AddChildAnalyzer(analyzer); + tcp->AddChildAnalyzer(pia->AsAnalyzer()); + conn->SetSessionAdapter(tcp, pia); + return tcp; + } + + void NextChunk(zeek::analyzer::Analyzer* analyzer, + zeek::detail::FuzzBuffer::Chunk& chunk) override + { + analyzer->NextStream(chunk.size, chunk.data.get(), chunk.is_orig); + } + }; + +class UDPFuzzer : public Fuzzer + { +public: + UDPFuzzer(const zeek::Tag& analyzer_tag) : Fuzzer(TRANSPORT_UDP, analyzer_tag) { } + + zeek::packet_analysis::IP::SessionAdapter* + BuildAnalyzerTree(zeek::Connection* conn, zeek::analyzer::Analyzer* analyzer) override + { + auto* udp = new zeek::packet_analysis::UDP::UDPSessionAdapter(conn); + auto* pia = new zeek::analyzer::pia::PIA_UDP(conn); + udp->AddChildAnalyzer(analyzer); + udp->AddChildAnalyzer(pia->AsAnalyzer()); + conn->SetSessionAdapter(udp, pia); + return udp; + } + + void NextChunk(zeek::analyzer::Analyzer* analyzer, + zeek::detail::FuzzBuffer::Chunk& chunk) override + { + analyzer->NextPacket(chunk.size, chunk.data.get(), chunk.is_orig); + } + }; + +// Create a Fuzzer given FUZZ_ANALYZER_NAME and FUZZ_ANALYZER_TRANSPORT globals. +std::unique_ptr Fuzzer::Create() + { + const auto& tag = zeek::analyzer_mgr->GetComponentTag(FUZZ_ANALYZER_NAME); + if ( ! tag ) + { + std::fprintf(stderr, "Unable to find component tag for '%s'", FUZZ_ANALYZER_NAME); abort(); } - tcp->AddChildAnalyzer(a); - tcp->AddChildAnalyzer(pia->AsAnalyzer()); - conn->SetSessionAdapter(tcp, pia); - return {a, tcp}; + if ( strcmp(FUZZ_ANALYZER_TRANSPORT, "tcp") == 0 ) + return std::make_unique(tag); + else if ( strcmp(FUZZ_ANALYZER_TRANSPORT, "udp") == 0 ) + return std::make_unique(tag); + + std::fprintf(stderr, "Unexpected FUZZ_ANALYZER_TRANSPORT '%s'", FUZZ_ANALYZER_TRANSPORT); + abort(); } +// Fuzzing entry point. extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { zeek::detail::FuzzBuffer fb{data, size}; @@ -60,33 +197,11 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) if ( ! fb.Valid() ) return 0; - auto tag = zeek::analyzer_mgr->GetComponentTag(TOSTRING(ZEEK_FUZZ_ANALYZER)); - auto conn = add_connection(); - auto [a, tcp] = add_analyzer(conn, tag); + std::unique_ptr fuzzer = Fuzzer::Create(); - for ( ;; ) - { - auto chunk = fb.Next(); + fuzzer->Process(fb); - if ( ! chunk ) - break; + fuzzer->Cleanup(); - try - { - a->NextStream(chunk->size, chunk->data.get(), chunk->is_orig); - } - catch ( const binpac::Exception& e ) - { - } - - chunk = {}; - zeek::event_mgr.Drain(); - - // Has the analyzer been disabled during event processing? - if ( ! tcp->HasChildAnalyzer(tag) ) - break; - } - - zeek::detail::fuzzer_cleanup_one_input(); return 0; }