zeek/scripts/base/frameworks/analyzer/dpd.zeek
Johanna Amann c72c1cba6f DPD: change handling of pre-confirmation violations, remove max_violations
This commit revamps the handling of analyzer violations that happen
before an analyzer confirms the protocol.

The current state is that an analyzer is disabled after 5 violations, if
it has not been confirmed. If it has been confirmed, it is disabled
after a single violation.

The reason for this is a historic mistake. In Zeek up to versions 1.5,
analyzers were unconditianally removed when they raised the first
protocol violation.

When this script was ported to the new layout for Zeek 2.0 in
b4b990cfb5, a logic error was introduced
that caused analyzers to no longer be disabled if they were not
confirmed.

This was the state for ~8 years, till the DPD::max_violations options
was added, which instates the current approach of disabling unconfirmed
analyzers after 5 violations. Sadly, there is not much discussion about
this change - from my hazy memory, I think this was discovered during
performance tests and the new behavior was added without checking into
the history of previous changes.

This commit reinstates the originally intended behavior of DPD. When an
analyzer that has not been confirmed raises a protocol violation, it is
immediately removed from the connection. This also makes a lot of sense
- this allows the analyzer to be in a "tasting" phase at the beginning
of the connection, and to error out quickly once it realizes that it was
attached to a connection not containing the desired protocol.

This change also removes the DPD::max_violations option, as it no longer
serves any purpose after this change. (In practice, the option remains
with an &deprecated warning, but it is no longer used for anything).

There are relatively minimal test-baseline changes due to this; they are
mostly triggered by the removal of the data structure and by less
analyzer errors being thrown, as unconfirmed analyzers are disabled
after the first error.
2025-01-30 16:59:44 +00:00

139 lines
4 KiB
Text

##! Activates port-independent protocol detection and selectively disables
##! analyzers if protocol violations occur.
module DPD;
export {
## Add the DPD logging stream identifier.
redef enum Log::ID += { LOG };
## A default logging policy hook for the stream.
global log_policy: Log::PolicyHook;
## The record type defining the columns to log in the DPD logging stream.
type Info: record {
## Timestamp for when protocol analysis failed.
ts: time &log;
## Connection unique ID.
uid: string &log;
## Connection ID containing the 4-tuple which identifies endpoints.
id: conn_id &log;
## Transport protocol for the violation.
proto: transport_proto &log;
## The analyzer that generated the violation.
analyzer: string &log;
## The textual reason for the analysis failure.
failure_reason: string &log;
};
## Deprecated, please see https://github.com/zeek/zeek/pull/4200 for details
option max_violations: table[Analyzer::Tag] of count = table() &deprecated="Remove in v8.1: This has become non-functional in Zeek 7.2, see PR #4200" &default = 5;
## Analyzers which you don't want to throw
option ignore_violations: set[Analyzer::Tag] = set();
## Ignore violations which go this many bytes into the connection.
## Set to 0 to never ignore protocol violations.
option ignore_violations_after = 10 * 1024;
}
redef record connection += {
dpd: Info &optional;
## The set of services (analyzers) for which Zeek has observed a
## violation after the same service had previously been confirmed.
service_violation: set[string] &default=set();
};
event zeek_init() &priority=5
{
Log::create_stream(DPD::LOG, [$columns=Info, $path="dpd", $policy=log_policy]);
}
event analyzer_confirmation_info(atype: AllAnalyzers::Tag, info: AnalyzerConfirmationInfo) &priority=10
{
if ( ! is_protocol_analyzer(atype) && ! is_packet_analyzer(atype) )
return;
if ( ! info?$c )
return;
local c = info$c;
local analyzer = Analyzer::name(atype);
add c$service[analyzer];
}
event analyzer_violation_info(atype: AllAnalyzers::Tag, info: AnalyzerViolationInfo) &priority=10
{
if ( ! is_protocol_analyzer(atype) && ! is_packet_analyzer(atype) )
return;
if ( ! info?$c )
return;
local c = info$c;
local analyzer = Analyzer::name(atype);
# If the service hasn't been confirmed yet, or already failed,
# don't generate a log message for the protocol violation.
if ( analyzer !in c$service || analyzer in c$service_violation )
return;
# No longer delete a service once it has been confirmed.
# FIXME: track failed analyzers somehow - either by changing how they are logged, or by adding a new column
# delete c$service[analyzer];
add c$service_violation[analyzer];
local dpd: Info;
dpd$ts = network_time();
dpd$uid = c$uid;
dpd$id = c$id;
dpd$proto = get_port_transport_proto(c$id$orig_p);
dpd$analyzer = analyzer;
# Encode data into the reason if there's any as done for the old
# analyzer_violation event, previously.
local reason = info$reason;
if ( info?$data )
{
local ellipsis = |info$data| > 40 ? "..." : "";
local data = info$data[0:40];
reason = fmt("%s [%s%s]", reason, data, ellipsis);
}
dpd$failure_reason = reason;
c$dpd = dpd;
}
event analyzer_violation_info(atype: AllAnalyzers::Tag, info: AnalyzerViolationInfo ) &priority=5
{
if ( ! is_protocol_analyzer(atype) && ! is_packet_analyzer(atype) )
return;
if ( ! info?$c || ! info?$aid )
return;
if ( atype in ignore_violations )
return;
local c = info$c;
local aid = info$aid;
local size = c$orig$size + c$resp$size;
if ( ignore_violations_after > 0 && size > ignore_violations_after )
return;
disable_analyzer(c$id, aid, F);
}
event analyzer_violation_info(atype: AllAnalyzers::Tag, info: AnalyzerViolationInfo ) &priority=-5
{
if ( ! is_protocol_analyzer(atype) && ! is_packet_analyzer(atype) )
return;
if ( ! info?$c )
return;
if ( info$c?$dpd )
{
Log::write(DPD::LOG, info$c$dpd);
delete info$c$dpd;
}
}