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
This commit is contained in:
Arne Welzel 2023-10-26 12:54:11 +02:00
parent 4d3e9de3d3
commit 5d781e54bd
2 changed files with 186 additions and 62 deletions

View file

@ -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

View file

@ -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 <binpac.h>
#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<zeek::analyzer::Analyzer*, zeek::packet_analysis::TCP::TCPSessionAdapter*>
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<zeek::analyzer::Analyzer*, zeek::packet_analysis::IP::SessionAdapter*,
zeek::Connection*>
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<Fuzzer> 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> 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<TCPFuzzer>(tag);
else if ( strcmp(FUZZ_ANALYZER_TRANSPORT, "udp") == 0 )
return std::make_unique<UDPFuzzer>(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 = 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;
}