Merge remote-tracking branch 'origin/topic/awelzel/dns-naming-authority-pointer'

* origin/topic/awelzel/dns-naming-authority-pointer:
  DNS: Implement NAPTR RR support
  DNS: Move extract_char_string() helper around
This commit is contained in:
Arne Welzel 2025-06-24 17:44:06 +02:00
commit fbeb3adfe6
13 changed files with 157 additions and 25 deletions

View file

@ -11,10 +11,37 @@
#include "zeek/Event.h"
#include "zeek/NetVar.h"
#include "zeek/RunState.h"
#include "zeek/Val.h"
#include "zeek/ZeekString.h"
#include "zeek/analyzer/protocol/dns/events.bif.h"
#include "zeek/session/Manager.h"
namespace {
zeek::StringValPtr extract_char_string(zeek::analyzer::Analyzer* analyzer, const u_char*& data, int& len, int& rdlen) {
if ( rdlen <= 0 )
return nullptr;
uint8_t str_size = data[0];
--rdlen;
--len;
++data;
if ( str_size > rdlen ) {
analyzer->Weird("DNS_TXT_char_str_past_rdlen");
return nullptr;
}
auto rval = zeek::make_intrusive<zeek::StringVal>(str_size, reinterpret_cast<const char*>(data));
rdlen -= str_size;
len -= str_size;
data += str_size;
return rval;
}
} // namespace
namespace zeek::analyzer::dns {
namespace detail {
@ -273,6 +300,8 @@ bool DNS_Interpreter::ParseAnswer(detail::DNS_MsgInfo* msg, const u_char*& data,
break;
case detail::TYPE_NAPTR: status = ParseRR_NAPTR(msg, data, len, rdlength, msg_start); break;
case detail::TYPE_EDNS: status = ParseRR_EDNS(msg, data, len, rdlength, msg_start); break;
case detail::TYPE_TKEY: status = ParseRR_TKEY(msg, data, len, rdlength, msg_start); break;
@ -602,6 +631,50 @@ bool DNS_Interpreter::ParseRR_SRV(detail::DNS_MsgInfo* msg, const u_char*& data,
return true;
}
bool DNS_Interpreter::ParseRR_NAPTR(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
const u_char* msg_start) {
zeek_uint_t order = ExtractShort(data, len);
zeek_uint_t preference = ExtractShort(data, len);
rdlength -= 4;
if ( len <= 0 || rdlength <= 0 ) {
analyzer->AnalyzerViolation("DNS_NAPTR_too_short");
return false;
}
// These all check rdlength and return nullptr if there's not enough data available.
auto flags = extract_char_string(analyzer, data, len, rdlength);
auto service = extract_char_string(analyzer, data, len, rdlength);
auto regexp = extract_char_string(analyzer, data, len, rdlength);
// The replacement string is a name. Compression shouldn't be used, but doesn't seem
// we have a helper that would allow to control this.
u_char replacement[513];
int replacement_len = sizeof(replacement) - 1;
u_char* replacement_end = ExtractName(data, len, replacement, replacement_len, msg_start, false);
if ( ! flags || ! service || ! regexp || ! replacement_end ) {
analyzer->AnalyzerViolation("DNS_NAPTR_RR_too_short");
return false;
}
if ( dns_NAPTR_reply && ! msg->skip_event ) {
static auto dns_naptr_rr = id::find_type<RecordType>("dns_naptr_rr");
auto r = make_intrusive<RecordVal>(dns_naptr_rr);
r->Assign(0, order);
r->Assign(1, preference);
r->Assign(2, std::move(flags));
r->Assign(3, std::move(service));
r->Assign(4, std::move(regexp));
r->Assign(5, zeek::make_intrusive<StringVal>(new String(replacement, replacement_end - replacement, true)));
analyzer->EnqueueConnEvent(dns_NAPTR_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(), r);
}
return true;
}
bool DNS_Interpreter::ParseRR_EDNS(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
const u_char* msg_start) {
if ( dns_EDNS_addl && ! msg->skip_event )
@ -1413,30 +1486,6 @@ bool DNS_Interpreter::ParseRR_WKS(detail::DNS_MsgInfo* msg, const u_char*& data,
return true;
}
static StringValPtr extract_char_string(analyzer::Analyzer* analyzer, const u_char*& data, int& len, int& rdlen) {
if ( rdlen <= 0 )
return nullptr;
uint8_t str_size = data[0];
--rdlen;
--len;
++data;
if ( str_size > rdlen ) {
analyzer->Weird("DNS_TXT_char_str_past_rdlen");
return nullptr;
}
auto rval = make_intrusive<StringVal>(str_size, reinterpret_cast<const char*>(data));
rdlen -= str_size;
len -= str_size;
data += str_size;
return rval;
}
bool DNS_Interpreter::ParseRR_HINFO(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength) {
if ( ! dns_HINFO_reply || msg->skip_event ) {
data += rdlength;

View file

@ -364,6 +364,7 @@ protected:
bool ParseRR_MX(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, const u_char* msg_start);
bool ParseRR_NBS(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, const u_char* msg_start);
bool ParseRR_SRV(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, const u_char* msg_start);
bool ParseRR_NAPTR(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, const u_char* msg_start);
bool ParseRR_EDNS(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, const u_char* msg_start);
bool ParseRR_EDNS_ECS(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
const u_char* msg_start);

View file

@ -467,6 +467,23 @@ event dns_CAA_reply%(c: connection, msg: dns_msg, ans: dns_answer, flags: count,
## dns_skip_addl dns_skip_all_addl dns_skip_all_auth dns_skip_auth
event dns_SRV_reply%(c: connection, msg: dns_msg, ans: dns_answer, target: string, priority: count, weight: count, p: count%);
## Generated for DNS replies of type *NAPTR*. For replies with multiple answers,
## an individual event of the corresponding type is raised for each.
##
## c: The connection, which may be UDP or TCP depending on the type of the
## transport-layer session being analyzed.
##
## msg: The parsed DNS message header.
##
## ans: The type-independent part of the parsed answer record.
##
## naptr: The parsed RDATA of NAPTR type record.
##
## .. zeek:see:: dns_AAAA_reply dns_A_reply dns_CNAME_reply dns_EDNS_addl
## dns_HINFO_reply dns_MX_reply dns_NS_reply dns_PTR_reply dns_SOA_reply
## dns_TSIG_addl dns_TXT_reply dns_SPF_reply dns_WKS_reply dns_SRV_reply dns_end
event dns_NAPTR_reply%(c: connection, msg: dns_msg, ans: dns_answer, naptr: dns_naptr_rr%);
## Generated on DNS reply resource records when the type of record is not one
## that Zeek knows how to parse and generate another more specific event.
##