Store error message from BPF compilation

This commit is contained in:
Tim Wojtulewicz 2022-08-12 09:13:41 -07:00
parent 767c83ede8
commit 82adecb2ad
8 changed files with 100 additions and 49 deletions

View file

@ -282,7 +282,7 @@ function install(): bool
$msg=fmt("Compiling packet filter failed"), $msg=fmt("Compiling packet filter failed"),
$sub=tmp_filter]); $sub=tmp_filter]);
local error_string = fmt("Bad pcap filter '%s'", tmp_filter); local error_string = fmt("Bad pcap filter '%s': %s", tmp_filter, Pcap::get_filter_state_string(DefaultPcapFilter));
local pkt_src_error : string = Pcap::error(); local pkt_src_error : string = Pcap::error();
if ( pkt_src_error != "no error" ) if ( pkt_src_error != "no error" )

View file

@ -78,8 +78,7 @@ BPF_Program::~BPF_Program()
FreeCode(); FreeCode();
} }
bool BPF_Program::Compile(pcap_t* pcap, const char* filter, uint32_t netmask, std::string& errbuf, bool BPF_Program::Compile(pcap_t* pcap, const char* filter, uint32_t netmask, bool optimize)
bool optimize)
{ {
if ( ! pcap ) if ( ! pcap )
return false; return false;
@ -88,7 +87,8 @@ bool BPF_Program::Compile(pcap_t* pcap, const char* filter, uint32_t netmask, st
if ( pcap_compile(pcap, &m_program, (char*)filter, optimize, netmask) < 0 ) if ( pcap_compile(pcap, &m_program, (char*)filter, optimize, netmask) < 0 )
{ {
errbuf = util::fmt("pcap_compile(%s): %s", filter, pcap_geterr(pcap)); state_message = std::string(pcap_geterr(pcap));
state = GetStateFromMessage(state_message);
return false; return false;
} }
@ -99,7 +99,7 @@ bool BPF_Program::Compile(pcap_t* pcap, const char* filter, uint32_t netmask, st
} }
bool BPF_Program::Compile(zeek_uint_t snaplen, int linktype, const char* filter, uint32_t netmask, bool BPF_Program::Compile(zeek_uint_t snaplen, int linktype, const char* filter, uint32_t netmask,
std::string& errbuf, bool optimize) bool optimize)
{ {
FreeCode(); FreeCode();
@ -120,12 +120,19 @@ bool BPF_Program::Compile(zeek_uint_t snaplen, int linktype, const char* filter,
int err = pcap_compile_nopcap(snaplen, linktype, &m_program, (char*)filter, optimize, netmask, int err = pcap_compile_nopcap(snaplen, linktype, &m_program, (char*)filter, optimize, netmask,
my_error); my_error);
if ( err < 0 ) if ( err < 0 )
errbuf = std::string(my_error); {
state = GetStateFromMessage(errstr);
state_message = util::fmt("pcap_compile(%s): %s", filter, pcap_geterr(pcap);
}
#else #else
int err = pcap_compile_nopcap(static_cast<int>(snaplen), linktype, &m_program, (char*)filter, optimize, netmask); int err = pcap_compile_nopcap(static_cast<int>(snaplen), linktype, &m_program, (char*)filter,
optimize, netmask);
// We have no way of knowing what the error actually was because pcap_compile_nocap doesn't
// return an error string nor any other information, so just assume every failure is
// fatal.
if ( err < 0 ) if ( err < 0 )
errbuf.clear(); state = FilterState::FATAL;
#endif #endif
if ( err == 0 ) if ( err == 0 )
@ -155,4 +162,12 @@ void BPF_Program::FreeCode()
} }
} }
FilterState BPF_Program::GetStateFromMessage(const std::string& err)
{
if ( err.find("filtering not implemented") != std::string::npos )
return FilterState::WARNING;
return FilterState::FATAL;
}
} // namespace zeek::iosource::detail } // namespace zeek::iosource::detail

View file

@ -4,6 +4,7 @@
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include "zeek/util.h" #include "zeek/util.h"
extern "C" extern "C"
@ -11,7 +12,17 @@ extern "C"
#include <pcap.h> #include <pcap.h>
} }
namespace zeek::iosource::detail namespace zeek::iosource
{
enum class FilterState : uint8_t
{
OK,
FATAL, // results in Reporter::Error
WARNING // results in Reporter::Warning
};
namespace detail
{ {
// BPF_Programs are an abstraction around struct bpf_program, // BPF_Programs are an abstraction around struct bpf_program,
@ -33,8 +44,7 @@ public:
* *
* @return true on successful compilation, false otherwise. * @return true on successful compilation, false otherwise.
*/ */
bool Compile(pcap_t* pcap, const char* filter, uint32_t netmask, std::string& errbuf, bool Compile(pcap_t* pcap, const char* filter, uint32_t netmask, bool optimize = true);
bool optimize = true);
/** /**
* Creates a BPF program when no pcap handle is available. The parameters match the usage * Creates a BPF program when no pcap handle is available. The parameters match the usage
@ -43,7 +53,7 @@ public:
* @return true on successful compilation, false otherwise. * @return true on successful compilation, false otherwise.
*/ */
bool Compile(zeek_uint_t snaplen, int linktype, const char* filter, uint32_t netmask, bool Compile(zeek_uint_t snaplen, int linktype, const char* filter, uint32_t netmask,
std::string& errbuf, bool optimize = true); bool optimize = true);
/** /**
* Returns true if this program currently contains compiled code, false otherwise. * Returns true if this program currently contains compiled code, false otherwise.
@ -61,14 +71,30 @@ public:
*/ */
bpf_program* GetProgram(); bpf_program* GetProgram();
/**
* Returns the state of the compilation process.
*/
FilterState GetState() const { return state; }
/**
* Returns an error message, if any, that was returned from the compliation process.
*/
std::string GetStateMessage() const { return state_message; }
protected: protected:
void FreeCode(); void FreeCode();
FilterState GetStateFromMessage(const std::string& err);
// (I like to prefix member variables with m_, makes it clear // (I like to prefix member variables with m_, makes it clear
// in the implementation whether it's a global or not. --ck) // in the implementation whether it's a global or not. --ck)
bool m_compiled = false; bool m_compiled = false;
bool m_matches_anything = false; bool m_matches_anything = false;
struct bpf_program m_program; struct bpf_program m_program;
FilterState state = FilterState::OK;
std::string state_message;
}; };
} // namespace zeek::iosource::detail } // namespace detail
} // namespace zeek::iosource

View file

@ -203,18 +203,17 @@ bool PktSrc::ExtractNextPacketInternal()
detail::BPF_Program* PktSrc::CompileFilter(const std::string& filter) detail::BPF_Program* PktSrc::CompileFilter(const std::string& filter)
{ {
std::string errbuf;
auto code = std::make_unique<detail::BPF_Program>(); auto code = std::make_unique<detail::BPF_Program>();
if ( ! code->Compile(BifConst::Pcap::snaplen, LinkType(), filter.c_str(), Netmask(), errbuf) ) if ( ! code->Compile(BifConst::Pcap::snaplen, LinkType(), filter.c_str(), Netmask()) )
{ {
std::string msg = util::fmt("cannot compile BPF filter \"%s\"", filter.c_str()); std::string msg = util::fmt("cannot compile BPF filter \"%s\"", filter.c_str());
if ( ! errbuf.empty() ) std::string state_msg = code->GetStateMessage();
msg += ": " + errbuf; if ( ! state_msg.empty() )
msg += ": " + state_msg;
Error(msg); Error(msg);
return nullptr;
} }
return code.release(); return code.release();
@ -225,10 +224,9 @@ bool PktSrc::PrecompileBPFFilter(int index, const std::string& filter)
if ( index < 0 ) if ( index < 0 )
return false; return false;
// Compile filter. // Compile filter. This will always return a pointer, but may have stored an error
// internally.
auto code = CompileFilter(filter); auto code = CompileFilter(filter);
if ( ! code )
return false;
// Store it in vector. // Store it in vector.
if ( index >= static_cast<int>(filters.size()) ) if ( index >= static_cast<int>(filters.size()) )
@ -239,7 +237,7 @@ bool PktSrc::PrecompileBPFFilter(int index, const std::string& filter)
filters[index] = code; filters[index] = code;
return true; return code->GetState() != FilterState::FATAL;
} }
detail::BPF_Program* PktSrc::GetBPFFilter(int index) detail::BPF_Program* PktSrc::GetBPFFilter(int index)

View file

@ -5,6 +5,7 @@
#include <sys/types.h> // for u_char #include <sys/types.h> // for u_char
#include <vector> #include <vector>
#include "zeek/iosource/BPF_Program.h"
#include "zeek/iosource/IOSource.h" #include "zeek/iosource/IOSource.h"
#include "zeek/iosource/Packet.h" #include "zeek/iosource/Packet.h"
@ -13,11 +14,6 @@ struct pcap_pkthdr;
namespace zeek::iosource namespace zeek::iosource
{ {
namespace detail
{
class BPF_Program;
}
/** /**
* Base class for packet sources. * Base class for packet sources.
*/ */
@ -102,7 +98,7 @@ public:
* Precompiles a BPF filter and associates the given index with it. * Precompiles a BPF filter and associates the given index with it.
* The compiled filter will be then available via \a GetBPFFilter(). * The compiled filter will be then available via \a GetBPFFilter().
* *
* This is primarily a helper for packet source implementation that * This is primarily a helper for packet source implementations that
* want to apply BPF filtering to their packets. * want to apply BPF filtering to their packets.
* *
* @param index The index to associate with the filter. * @param index The index to associate with the filter.
@ -139,7 +135,8 @@ public:
* *
* @param pkt The content of the packet to filter. * @param pkt The content of the packet to filter.
* *
* @return True if it maches. */ * @return True if it matches.
*/
bool ApplyBPFFilter(int index, const struct pcap_pkthdr* hdr, const u_char* pkt); bool ApplyBPFFilter(int index, const struct pcap_pkthdr* hdr, const u_char* pkt);
/** /**
@ -158,9 +155,9 @@ public:
* Precompiles a filter and associates a given index with it. The * Precompiles a filter and associates a given index with it. The
* filter syntax is defined by the packet source's implenentation. * filter syntax is defined by the packet source's implenentation.
* *
* Derived classes must implement this to implement their filtering. * Derived classes can override this method to implement their own
* If they want to use BPF but don't support it natively, they can * filtering. If not overriden, it uses the pcap-based BPF filtering
* call the corresponding helper method provided by \a PktSrc. * by default.
* *
* @param index The index to associate with the filter * @param index The index to associate with the filter
* *
@ -169,7 +166,10 @@ public:
* @return True on success, false if a problem occurred or filtering * @return True on success, false if a problem occurred or filtering
* is not supported. * is not supported.
*/ */
virtual bool PrecompileFilter(int index, const std::string& filter) = 0; virtual bool PrecompileFilter(int index, const std::string& filter)
{
return PrecompileBPFFilter(index, filter);
}
/** /**
* Activates a precompiled filter with the given index. * Activates a precompiled filter with the given index.
@ -336,6 +336,16 @@ protected:
*/ */
virtual void DoneWithPacket() = 0; virtual void DoneWithPacket() = 0;
/**
* Performs the actual filter compilation. This can be overridden to
* provide a different implementation of the compiilation called by
* PrecompileBPFFilter(). This is primarily used by the pcap source
* use a different version of BPF_Filter::Compile;
*
* @param filter the filtering string being compiled.
*
* @return The compiled filter or nullptr if compilation failed.
*/
virtual detail::BPF_Program* CompileFilter(const std::string& filter); virtual detail::BPF_Program* CompileFilter(const std::string& filter);
private: private:

View file

@ -263,25 +263,19 @@ void PcapSource::DoneWithPacket()
// Nothing to do. // Nothing to do.
} }
bool PcapSource::PrecompileFilter(int index, const std::string& filter)
{
return PktSrc::PrecompileBPFFilter(index, filter);
}
detail::BPF_Program* PcapSource::CompileFilter(const std::string& filter) detail::BPF_Program* PcapSource::CompileFilter(const std::string& filter)
{ {
std::string errbuf;
auto code = std::make_unique<detail::BPF_Program>(); auto code = std::make_unique<detail::BPF_Program>();
if ( ! code->Compile(pd, filter.c_str(), Netmask(), errbuf) ) if ( ! code->Compile(pd, filter.c_str(), Netmask()) )
{ {
std::string msg = util::fmt("cannot compile BPF filter \"%s\"", filter.c_str()); std::string msg = util::fmt("cannot compile BPF filter \"%s\"", filter.c_str());
if ( ! errbuf.empty() ) std::string state_msg = code->GetStateMessage();
msg += ": " + errbuf; if ( ! state_msg.empty() )
msg += ": " + state_msg;
Error(msg); Error(msg);
return nullptr;
} }
return code.release(); return code.release();
@ -310,14 +304,16 @@ bool PcapSource::SetFilter(int index)
// since the default scripts will always attempt to compile // since the default scripts will always attempt to compile
// and install a default filter // and install a default filter
} }
else else if ( auto program = code->GetProgram() )
{ {
if ( pcap_setfilter(pd, code->GetProgram()) < 0 ) if ( pcap_setfilter(pd, program) < 0 )
{ {
PcapError(); PcapError();
return false; return false;
} }
} }
else if ( code->GetState() != FilterState::OK )
return false;
#ifndef HAVE_LINUX #ifndef HAVE_LINUX
// Linux doesn't clear counters when resetting filter. // Linux doesn't clear counters when resetting filter.

View file

@ -28,9 +28,9 @@ protected:
void Close() override; void Close() override;
bool ExtractNextPacket(Packet* pkt) override; bool ExtractNextPacket(Packet* pkt) override;
void DoneWithPacket() override; void DoneWithPacket() override;
bool PrecompileFilter(int index, const std::string& filter) override;
bool SetFilter(int index) override; bool SetFilter(int index) override;
void Statistics(Stats* stats) override; void Statistics(Stats* stats) override;
detail::BPF_Program* CompileFilter(const std::string& filter) override; detail::BPF_Program* CompileFilter(const std::string& filter) override;
private: private:

View file

@ -8,6 +8,7 @@ const bufsize: count;
%%{ %%{
#include <pcap.h> #include <pcap.h>
#include "zeek/iosource/BPF_Program.h"
#include "zeek/iosource/Manager.h" #include "zeek/iosource/Manager.h"
%%} %%}
@ -44,8 +45,13 @@ function precompile_pcap_filter%(id: PcapFilterID, s: string%): bool
bool success = true; bool success = true;
zeek::iosource::PktSrc* ps = zeek::iosource_mgr->GetPktSrc(); zeek::iosource::PktSrc* ps = zeek::iosource_mgr->GetPktSrc();
if ( ps && ! ps->PrecompileFilter(id->AsInt(), s->CheckString()) ) if ( ps )
success = false; {
bool compiled = ps->PrecompileFilter(id->AsInt(), s->CheckString());
auto filter = ps->GetBPFFilter(id->AsInt());
if ( ! compiled || ( filter && filter->GetState() != zeek::iosource::FilterState::OK ) )
success = false;
}
return zeek::val_mgr->Bool(success); return zeek::val_mgr->Bool(success);
%} %}
@ -99,7 +105,7 @@ function error%(%): string
if ( ps ) if ( ps )
{ {
const char* err = ps->ErrorMsg(); const char* err = ps->ErrorMsg();
if ( *err ) if ( err && *err )
return zeek::make_intrusive<zeek::StringVal>(err); return zeek::make_intrusive<zeek::StringVal>(err);
} }