Merge remote-tracking branch 'origin/topic/jsiwek/gh-532-improve-disable-analyzer'

Includes fix for potential iterator invalidation during iteration.

* origin/topic/jsiwek/gh-532-improve-disable-analyzer:
  GH-532: improve disable_analyzer BIF
This commit is contained in:
Robin Sommer 2019-08-16 18:45:39 +00:00
commit 8ab0650c1e
15 changed files with 263 additions and 60 deletions

14
CHANGES
View file

@ -1,4 +1,18 @@
3.1.0-dev.58 | 2019-08-16 18:45:39 +0000
* GH-532: Improve disable_analyzer BIF. (Jon Siwek, Corelight)
- Add an extra "prevent" parameter (default value of false), which
helps prevent the same analyzer type from being attached in the
future.
- Fixes disable_analyzer() to work when called even earlier, like
within the protocol_confirmation event.
- Fixes disable_analyzer() when called on an analyzer added to the
tree via TCP_Analyzer::AddChildPacketAnalyzer.
3.1.0-dev.55 | 2019-08-14 16:18:44 -0700
* Fix misc. Coverity warnings (Jon Siwek, Corelight)

View file

@ -1 +1 @@
3.1.0-dev.55
3.1.0-dev.58

2
doc

@ -1 +1 @@
Subproject commit 9305032f1ae14ec8a988ecd4e6217ef16d7c6415
Subproject commit fb4d8cda00c30d7ae37b864521b14447fff5586b

View file

@ -91,12 +91,6 @@ bool Analyzer::IsAnalyzer(const char* name)
return strcmp(analyzer_mgr->GetComponentName(tag).c_str(), name) == 0;
}
// Used in debugging output.
static string fmt_analyzer(Analyzer* a)
{
return string(a->GetAnalyzerName()) + fmt("[%d]", a->GetID());
}
Analyzer::Analyzer(const char* name, Connection* conn)
{
Tag tag = analyzer_mgr->GetComponentTag(name);
@ -381,7 +375,11 @@ void Analyzer::ForwardEndOfData(bool orig)
bool Analyzer::AddChildAnalyzer(Analyzer* analyzer, bool init)
{
if ( HasChildAnalyzer(analyzer->GetAnalyzerTag()) )
auto t = analyzer->GetAnalyzerTag();
auto it = std::find(prevented.begin(), prevented.end(), t);
auto prevent = (it != prevented.end());
if ( HasChildAnalyzer(t) || prevent )
{
analyzer->Done();
delete analyzer;
@ -407,46 +405,67 @@ bool Analyzer::AddChildAnalyzer(Analyzer* analyzer, bool init)
Analyzer* Analyzer::AddChildAnalyzer(Tag analyzer)
{
if ( ! HasChildAnalyzer(analyzer) )
{
if ( HasChildAnalyzer(analyzer) )
return nullptr;
auto it = std::find(prevented.begin(), prevented.end(), analyzer);
if ( it != prevented.end() )
return nullptr;
Analyzer* a = analyzer_mgr->InstantiateAnalyzer(analyzer, conn);
if ( a && AddChildAnalyzer(a) )
return a;
return nullptr;
}
return 0;
}
bool Analyzer::RemoveChild(const analyzer_list& children, ID id)
{
for ( const auto& i : children )
{
if ( i->id != id )
continue;
if ( i->finished || i->removing )
return false;
void Analyzer::RemoveChildAnalyzer(Analyzer* analyzer)
{
LOOP_OVER_CHILDREN(i)
if ( *i == analyzer && ! (analyzer->finished || analyzer->removing) )
{
DBG_LOG(DBG_ANALYZER, "%s disabling child %s",
fmt_analyzer(this).c_str(), fmt_analyzer(*i).c_str());
fmt_analyzer(this).c_str(), fmt_analyzer(i).c_str());
// We just flag it as being removed here but postpone
// actually doing that to later. Otherwise, we'd need
// to call Done() here, which then in turn might
// cause further code to be executed that may assume
// something not true because of a violation that
// triggered the removal in the first place.
(*i)->removing = true;
return;
}
i->removing = true;
return true;
}
void Analyzer::RemoveChildAnalyzer(ID id)
{
LOOP_OVER_CHILDREN(i)
if ( (*i)->id == id && ! ((*i)->finished || (*i)->removing) )
{
DBG_LOG(DBG_ANALYZER, "%s disabling child %s",
fmt_analyzer(this).c_str(), fmt_analyzer(*i).c_str());
// See comment above.
(*i)->removing = true;
return;
return false;
}
bool Analyzer::RemoveChildAnalyzer(ID id)
{
return RemoveChild(children, id) || RemoveChild(new_children, id);
}
bool Analyzer::Remove()
{
assert(parent);
parent->RemoveChildAnalyzer(this);
return removing;
}
void Analyzer::PreventChildren(Tag tag)
{
auto it = std::find(prevented.begin(), prevented.end(), tag);
if ( it != prevented.end() )
return;
prevented.emplace_back(tag);
}
bool Analyzer::HasChildAnalyzer(Tag tag)

View file

@ -4,6 +4,7 @@
#define ANALYZER_ANALYZER_H
#include <list>
#include <vector>
#include "Tag.h"
@ -318,6 +319,12 @@ public:
*/
bool IsFinished() const { return finished; }
/**
* Returns true if the analyzer has been flagged for removal and
* shouldn't be used anymore.
*/
bool Removing() const { return removing; }
/**
* Returns the tag associated with the analyzer's type.
*/
@ -349,19 +356,19 @@ public:
/**
* Adds a new child analyzer to the analyzer tree. If an analyzer of
* the same type already exists, the one passes in is silenty
* discarded.
* the same type already exists or is prevented, the one passed in is
* silently discarded.
*
* @param analyzer The ananlyzer to add. Takes ownership.
* @return false if analyzer type was already a child, else true.
* @return false if analyzer type was already a child or prevented, else true.
*/
bool AddChildAnalyzer(Analyzer* analyzer)
{ return AddChildAnalyzer(analyzer, true); }
/**
* Adds a new child analyzer to the analyzer tree. If an analyzer of
* the same type already exists, the one passes in is silenty
* discarded.
* the same type already exists or is prevented, the one passed in is
* silently discarded.
*
* @param tag The type of analyzer to add.
* @return the new analyzer instance that was added.
@ -373,16 +380,30 @@ public:
* child, in which case the method does nothing.
*
* @param analyzer The analyzer to remove.
*
* @return whether the child analyzer is scheduled for removal
* (and was not before).
*/
void RemoveChildAnalyzer(Analyzer* analyzer);
bool RemoveChildAnalyzer(Analyzer* analyzer)
{ return RemoveChildAnalyzer(analyzer->GetID()); }
/**
* Removes a child analyzer. It's ok for the analyzer to not to be a
* child, in which case the method does nothing.
*
* @param tag The type of analyzer to remove.
* @param id The type of analyzer to remove.
*
* @return whether the child analyzer is scheduled for removal
* (and was not before).
*/
void RemoveChildAnalyzer(ID id);
virtual bool RemoveChildAnalyzer(ID id);
/**
* Prevents an analyzer type from ever being added as a child.
*
* @param tag The type of analyzer to prevent.
*/
void PreventChildren(Tag tag);
/**
* Returns true if analyzer has a direct child of a given type.
@ -450,8 +471,10 @@ public:
/**
* Remove the analyzer form its parent. The analyzer must have a
* parent associated with it.
*
* @return whether the analyzer is being removed
*/
void Remove() { assert(parent); parent->RemoveChildAnalyzer(this); }
bool Remove();
/**
* Appends a support analyzer to the current list.
@ -570,6 +593,13 @@ protected:
friend class ::Connection;
friend class tcp::TCP_ApplicationAnalyzer;
/**
* Return a string represantation of an analyzer, containing its name
* and ID.
*/
static string fmt_analyzer(const Analyzer* a)
{ return string(a->GetAnalyzerName()) + fmt("[%d]", a->GetID()); }
/**
* Associates a connection with this analyzer. Must be called if
* using the default ctor.
@ -644,10 +674,10 @@ protected:
void AppendNewChildren();
/**
* Returns true if the analyzer has been flagged for removal and
* shouldn't be used otherwise anymore.
* Returns true if the child analyzer is now scheduled to be
* removed (and was not before)
*/
bool Removing() const { return removing; }
bool RemoveChild(const analyzer_list& children, ID id);
private:
// Internal method to eventually delete a child analyzer that's
@ -670,6 +700,7 @@ private:
SupportAnalyzer* resp_supporters;
analyzer_list new_children;
std::vector<Tag> prevented;
bool protocol_confirmed;

View file

@ -282,10 +282,11 @@ void PIA_TCP::ActivateAnalyzer(analyzer::Tag tag, const Rule* rule)
return;
}
if ( Parent()->HasChildAnalyzer(tag) )
analyzer::Analyzer* a = Parent()->AddChildAnalyzer(tag);
if ( ! a )
return;
analyzer::Analyzer* a = Parent()->AddChildAnalyzer(tag);
a->SetSignature(rule);
// We have two cases here:

View file

@ -205,6 +205,15 @@ analyzer::Analyzer* TCP_Analyzer::FindChild(Tag arg_tag)
return 0;
}
bool TCP_Analyzer::RemoveChildAnalyzer(ID id)
{
auto rval = analyzer::TransportLayerAnalyzer::RemoveChildAnalyzer(id);
if ( rval )
return rval;
return RemoveChild(packet_children, id);
}
void TCP_Analyzer::EnableReassembly()
{
@ -1209,8 +1218,28 @@ void TCP_Analyzer::DeliverPacket(int len, const u_char* data, bool is_orig,
// Handle child_packet analyzers. Note: This happens *after* the
// packet has been processed and the TCP state updated.
LOOP_OVER_GIVEN_CHILDREN(i, packet_children)
(*i)->NextPacket(len, data, is_orig, rel_data_seq, ip, caplen);
analyzer_list::iterator next;
for ( auto i = packet_children.begin(); i != packet_children.end(); /* nop */ )
{
auto child = *i;
if ( child->IsFinished() || child->Removing() )
{
if ( child->Removing() )
child->Done();
DBG_LOG(DBG_ANALYZER, "%s deleted child %s",
fmt_analyzer(this).c_str(), fmt_analyzer(child).c_str());
i = packet_children.erase(i);
delete child;
}
else
{
child->NextPacket(len, data, is_orig, rel_data_seq, ip, caplen);
++i;
}
}
if ( ! reassembling )
ForwardPacket(len, data, is_orig, rel_data_seq, ip, caplen);

View file

@ -36,6 +36,7 @@ public:
Analyzer* FindChild(ID id) override;
Analyzer* FindChild(Tag tag) override;
bool RemoveChildAnalyzer(ID id) override;
// True if the connection has closed in some sense, false otherwise.
int IsClosed() const { return orig->did_close || resp->did_close; }

View file

@ -4223,11 +4223,18 @@ function file_mode%(mode: count%): string
##
## aid: The analyzer ID.
##
## err_if_no_conn: Emit an error message if the connection does not exit.
##
## prevent: Prevent the same analyzer type from being attached in the future.
## This is useful for preventing the same analyzer from being
## automatically reattached in the future, e.g. as a result of a
## DPD signature suddenly matching.
##
## Returns: True if the connection identified by *cid* exists and has analyzer
## *aid*.
## *aid* and it is scheduled for removal.
##
## .. zeek:see:: Analyzer::schedule_analyzer Analyzer::name
function disable_analyzer%(cid: conn_id, aid: count, err_if_no_conn: bool &default=T%) : bool
function disable_analyzer%(cid: conn_id, aid: count, err_if_no_conn: bool &default=T, prevent: bool &default=F%) : bool
%{
Connection* c = sessions->FindConnection(cid);
if ( ! c )
@ -4244,8 +4251,11 @@ function disable_analyzer%(cid: conn_id, aid: count, err_if_no_conn: bool &defau
return val_mgr->GetBool(0);
}
a->Remove();
return val_mgr->GetBool(1);
if ( prevent )
a->Parent()->PreventChildren(a->GetAnalyzerTag());
auto rval = a->Remove();
return val_mgr->GetBool(rval);
%}
## Informs Zeek that it should skip any further processing of the contents of

View file

@ -0,0 +1,6 @@
proto confirm, Analyzer::ANALYZER_HTTP
T
http_request, GET, /style/enhanced.css
total http messages, {
[[orig_h=192.168.1.104, orig_p=1673/tcp, resp_h=63.245.209.11, resp_p=80/tcp]] = 1
}

View file

@ -0,0 +1,2 @@
triggered packets, [orig_h=192.168.1.104, orig_p=1673/tcp, resp_h=63.245.209.11, resp_p=80/tcp], 1, T
T

View file

@ -0,0 +1,6 @@
proto confirm, Analyzer::ANALYZER_HTTP
http_request, GET, /style/enhanced.css
T
total http messages, {
[[orig_h=192.168.1.104, orig_p=1673/tcp, resp_h=63.245.209.11, resp_p=80/tcp]] = 1
}

View file

@ -0,0 +1,34 @@
# @TEST-EXEC: zeek -b -r $TRACES/http/pipelined-requests.trace %INPUT >out
# @TEST-EXEC: btest-diff out
@load base/protocols/http
global msg_count: table[conn_id] of count &default=0;
event protocol_confirmation(c: connection, atype: Analyzer::Tag, aid: count) &priority=10
{
if ( atype != Analyzer::ANALYZER_HTTP )
return;
print "proto confirm", atype;
print disable_analyzer(c$id, aid, T, T);
}
event http_request(c: connection, method: string, original_URI: string, unescaped_URI: string, version: string)
{
++msg_count[c$id];
print "http_request", method, original_URI;
}
event http_reply(c: connection, version: string, code: count, reason: string)
{
++msg_count[c$id];
print "http_reply", code;
}
event zeek_done()
{
print "total http messages", msg_count;
}

View file

@ -0,0 +1,16 @@
# @TEST-EXEC: zeek -b -r $TRACES/http/pipelined-requests.trace %INPUT >out
# @TEST-EXEC: btest-diff out
@load base/protocols/http
event connection_established(c: connection)
{
set_current_conn_packets_threshold(c$id, 1, T);
}
event conn_packets_threshold_crossed(c: connection, threshold: count, is_orig: bool)
{
print "triggered packets", c$id, threshold, is_orig;
set_current_conn_packets_threshold(c$id, threshold + 1, T);
print disable_analyzer(c$id, current_analyzer(), T);
}

View file

@ -0,0 +1,34 @@
# @TEST-EXEC: zeek -b -r $TRACES/http/pipelined-requests.trace %INPUT >out
# @TEST-EXEC: btest-diff out
@load base/protocols/http
global msg_count: table[conn_id] of count &default=0;
event protocol_confirmation(c: connection, atype: Analyzer::Tag, aid: count) &priority=10
{
if ( atype != Analyzer::ANALYZER_HTTP )
return;
print "proto confirm", atype;
}
event http_request(c: connection, method: string, original_URI: string, unescaped_URI: string, version: string)
{
++msg_count[c$id];
print "http_request", method, original_URI;
print disable_analyzer(c$id, current_analyzer(), T, T);
}
event http_reply(c: connection, version: string, code: count, reason: string)
{
++msg_count[c$id];
print "http_reply", code;
}
event zeek_done()
{
print "total http messages", msg_count;
}