resolved: let's track fragment sizes of servers/retry on fragmenting

Fragmenting sucks, let's avoid it. Thus let's start tracking the maximum
fragment size we receive.

Also, let's redo a transaction via TCP if we see fragmenting on UDP, as
effective mitigation against DNS fragment attacks.
This commit is contained in:
Lennart Poettering
2021-02-18 15:55:58 +01:00
parent d79677ab44
commit acbf761b5d
5 changed files with 77 additions and 15 deletions
+14
View File
@@ -2626,6 +2626,20 @@ int dns_packet_has_nsid_request(DnsPacket *p) {
return has_nsid;
}
size_t dns_packet_size_unfragmented(DnsPacket *p) {
assert(p);
if (p->fragsize == 0) /* Wasn't fragmented */
return p->size;
/* The fragment size (p->fragsize) covers the whole (fragmented) IP packet, while the regular packet
* size (p->size) only covers the DNS part. Thus, subtract the UDP header from the largest fragment
* size, in order to determine which size of DNS packet would have gone through without
* fragmenting. */
return LESS_BY(p->fragsize, udp_header_size(p->family));
}
static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = {
[DNS_RCODE_SUCCESS] = "SUCCESS",
[DNS_RCODE_FORMERR] = "FORMERR",
+10
View File
@@ -152,6 +152,14 @@ static inline bool DNS_PACKET_VERSION_SUPPORTED(DnsPacket *p) {
return DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(p->opt);
}
static inline bool DNS_PACKET_IS_FRAGMENTED(DnsPacket *p) {
assert(p);
/* For ingress packets: was this packet fragmented according to our knowledge? */
return p->fragsize != 0;
}
/* LLMNR defines some bits differently */
#define DNS_PACKET_LLMNR_C(p) DNS_PACKET_AA(p)
#define DNS_PACKET_LLMNR_T(p) DNS_PACKET_RD(p)
@@ -325,3 +333,5 @@ static inline size_t udp_header_size(int af) {
assert_not_reached("Unexpected address family");
}
}
size_t dns_packet_size_unfragmented(DnsPacket *p);
+22 -9
View File
@@ -255,7 +255,7 @@ static void dns_server_reset_counters(DnsServer *s) {
* incomplete. */
}
void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, size_t size) {
void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, size_t fragsize) {
assert(s);
if (protocol == IPPROTO_UDP) {
@@ -289,10 +289,10 @@ void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLeve
dns_server_verified(s, level);
/* Remember the size of the largest UDP packet we received from a server, we know that we can always
* announce support for packets with at least this size. */
if (protocol == IPPROTO_UDP && s->received_udp_packet_max < size)
s->received_udp_packet_max = size;
/* Remember the size of the largest UDP packet fragment we received from a server, we know that we
* can always announce support for packets with at least this size. */
if (protocol == IPPROTO_UDP && s->received_udp_fragment_max < fragsize)
s->received_udp_fragment_max = fragsize;
}
void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level) {
@@ -389,6 +389,19 @@ void dns_server_packet_do_off(DnsServer *s, DnsServerFeatureLevel level) {
s->packet_do_off = true;
}
void dns_server_packet_udp_fragmented(DnsServer *s, size_t fragsize) {
assert(s);
/* Invoked whenever we got a fragmented UDP packet. Let's do two things: keep track of the largest
* fragment we ever received from the server, and remember this, so that we can use it to lower the
* advertised packet size in EDNS0 */
if (s->received_udp_fragment_max < fragsize)
s->received_udp_fragment_max = fragsize;
s->packet_fragmented = true;
}
static bool dns_server_grace_period_expired(DnsServer *s) {
usec_t ts;
@@ -607,7 +620,7 @@ int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeature
if (level == DNS_SERVER_FEATURE_LEVEL_LARGE)
packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX;
else
packet_size = server->received_udp_packet_max;
packet_size = server->received_udp_fragment_max;
return dns_packet_append_opt(packet, packet_size, edns_do, /* include_rfc6975 = */ true, NULL, 0, NULL);
}
@@ -919,7 +932,7 @@ void dns_server_reset_features(DnsServer *s) {
s->verified_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID;
s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST;
s->received_udp_packet_max = DNS_PACKET_UNICAST_SIZE_MAX;
s->received_udp_fragment_max = DNS_PACKET_UNICAST_SIZE_MAX;
s->packet_bad_opt = false;
s->packet_rrsig_missing = false;
@@ -979,7 +992,7 @@ void dns_server_dump(DnsServer *s, FILE *f) {
fputc('\n', f);
fprintf(f,
"\tMaximum UDP packet size received: %zu\n"
"\tMaximum UDP fragment size received: %zu\n"
"\tFailed UDP attempts: %u\n"
"\tFailed TCP attempts: %u\n"
"\tSeen truncated packet: %s\n"
@@ -987,7 +1000,7 @@ void dns_server_dump(DnsServer *s, FILE *f) {
"\tSeen RRSIG RR missing: %s\n"
"\tSeen invalid packet: %s\n"
"\tServer dropped DO flag: %s\n",
s->received_udp_packet_max,
s->received_udp_fragment_max,
s->n_failed_udp,
s->n_failed_tcp,
yes_no(s->packet_truncated),
+4 -2
View File
@@ -75,7 +75,7 @@ struct DnsServer {
DnsServerFeatureLevel verified_feature_level;
DnsServerFeatureLevel possible_feature_level;
size_t received_udp_packet_max;
size_t received_udp_fragment_max; /* largest packet or fragment (without IP/UDP header) we saw so far */
unsigned n_failed_udp;
unsigned n_failed_tcp;
@@ -86,6 +86,7 @@ struct DnsServer {
bool packet_rrsig_missing:1; /* Set when RRSIG was missing */
bool packet_invalid:1; /* Set when we failed to parse a reply */
bool packet_do_off:1; /* Set when the server didn't copy DNSSEC DO flag from request to response */
bool packet_fragmented:1; /* Set when we ever saw a fragmented packet */
usec_t verified_usec;
usec_t features_grace_period_usec;
@@ -118,7 +119,7 @@ DnsServer* dns_server_unref(DnsServer *s);
void dns_server_unlink(DnsServer *s);
void dns_server_move_back_and_unmark(DnsServer *s);
void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, size_t size);
void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, size_t fragsize);
void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level);
void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level);
@@ -126,6 +127,7 @@ void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_rcode_downgrade(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_invalid(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_do_off(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_udp_fragmented(DnsServer *s, size_t fragsize);
DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s);
+27 -4
View File
@@ -1031,6 +1031,7 @@ static int dns_transaction_fix_rcode(DnsTransaction *t) {
}
void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypted) {
bool retry_with_tcp = false;
int r;
assert(t);
@@ -1193,9 +1194,29 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt
return;
}
log_debug("Reply truncated, retrying via TCP.");
/* Response was truncated, let's try again with good old TCP */
log_debug("Reply truncated, retrying via TCP.");
retry_with_tcp = true;
} else if (t->scope->protocol == DNS_PROTOCOL_DNS &&
DNS_PACKET_IS_FRAGMENTED(p)) {
/* Report the fragment size, so that we downgrade from LARGE to regular EDNS0 if needed */
if (t->server)
dns_server_packet_udp_fragmented(t->server, dns_packet_size_unfragmented(p));
if (t->current_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) {
/* Packet was fragmented. Let's retry with TCP to avoid fragmentation attack
* issues. (We don't do that on the lowest feature level however, since crappy DNS
* servers often do not implement TCP, hence falling back to TCP on fragmentation is
* counter-productive there.) */
log_debug("Reply fragmented, retrying via TCP.");
retry_with_tcp = true;
}
}
if (retry_with_tcp) {
r = dns_transaction_emit_tcp(t);
if (r == -ESRCH) {
/* No servers found? Damn! */
@@ -1296,8 +1317,10 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt
if (DNS_PACKET_DO(t->sent) && !DNS_PACKET_DO(t->received))
dns_server_packet_do_off(t->server, t->current_feature_level);
/* Report that we successfully received a packet */
dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, p->size);
/* Report that we successfully received a packet. We keep track of the largest packet
* size/fragment size we got. Which is useful for announcing the EDNS(0) packet size we can
* receive to our server. */
dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, dns_packet_size_unfragmented(p));
}
/* See if we know things we didn't know before that indicate we better restart the lookup immediately. */