]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolved: add an automatic downgrade to non-DNSSEC mode
authorLennart Poettering <lennart@poettering.net>
Fri, 25 Dec 2015 14:05:46 +0000 (15:05 +0100)
committerLennart Poettering <lennart@poettering.net>
Sat, 26 Dec 2015 18:09:11 +0000 (19:09 +0100)
This adds a mode that makes resolved automatically downgrade from DNSSEC
support to classic non-DNSSEC resolving if the configured DNS server is
not capable of DNSSEC. Enabling this mode increases compatibility with
crappy network equipment, but of course opens up the system to
downgrading attacks.

The new mode can be enabled by setting DNSSEC=downgrade-ok in
resolved.conf. DNSSEC=yes otoh remains a "strict" mode, where DNS
resolving rather fails then allow downgrading.

Downgrading is done:

- when the server does not support EDNS0+DO
- or when the server supports it but does not augment returned RRs with
  RRSIGs. The latter is detected when requesting DS or SOA RRs for the
  root domain (which is necessary to do proofs for unsigned data)

src/resolve/resolved-dns-dnssec.c
src/resolve/resolved-dns-dnssec.h
src/resolve/resolved-dns-server.c
src/resolve/resolved-dns-server.h
src/resolve/resolved-dns-transaction.c

index 14fa58c54484cae046720bed63eee05a84e567a2..a856f0717efcbede9d540a3ab866deb7b512bcfa 100644 (file)
@@ -1197,6 +1197,7 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
 
 static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
         [DNSSEC_NO] = "no",
+        [DNSSEC_DOWNGRADE_OK] = "downgrade-ok",
         [DNSSEC_YES] = "yes",
 };
 DEFINE_STRING_TABLE_LOOKUP(dnssec_mode, DnssecMode);
@@ -1211,5 +1212,6 @@ static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
         [DNSSEC_UNSIGNED] = "unsigned",
         [DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
         [DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
+        [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server",
 };
 DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);
index 9ad20c8c695e6bbe3d9f18ceb7ace90591651d59..d7aecbce13fb65e9fc72b48b1b36f27e5904b0dd 100644 (file)
@@ -32,7 +32,14 @@ enum DnssecMode {
         /* No DNSSEC validation is done */
         DNSSEC_NO,
 
-        /* Validate locally, if the server knows DO, but if not, don't. Don't trust the AD bit */
+        /* Validate locally, if the server knows DO, but if not,
+         * don't. Don't trust the AD bit. If the server doesn't do
+         * DNSSEC properly, downgrade to non-DNSSEC operation. Of
+         * course, we then are vulnerable to a downgrade attack, but
+         * that's life and what is configured. */
+        DNSSEC_DOWNGRADE_OK,
+
+        /* Insist on DNSSEC server support, and rather fail than downgrading. */
         DNSSEC_YES,
 
         _DNSSEC_MODE_MAX,
@@ -54,6 +61,8 @@ enum DnssecResult {
         DNSSEC_UNSIGNED,
         DNSSEC_FAILED_AUXILIARY,
         DNSSEC_NSEC_MISMATCH,
+        DNSSEC_INCOMPATIBLE_SERVER,
+
         _DNSSEC_RESULT_MAX,
         _DNSSEC_RESULT_INVALID = -1
 };
index d565f99c09fe4b0cf82af173c65c41843885a0fc..b0db5bbb164cb72339dc20c0f71bde6b8f2b5eab 100644 (file)
@@ -228,9 +228,11 @@ void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, us
         assert(s);
 
         if (features == DNS_SERVER_FEATURE_LEVEL_LARGE) {
-                /* even if we successfully receive a reply to a request announcing
-                   support for large packets, that does not mean we can necessarily
-                   receive large packets. */
+                /* Even if we successfully receive a reply to a
+                   request announcing support for large packets, that
+                   does not mean we can necessarily receive large
+                   packets. */
+
                 if (s->verified_features < DNS_SERVER_FEATURE_LEVEL_LARGE - 1) {
                         s->verified_features = DNS_SERVER_FEATURE_LEVEL_LARGE - 1;
                         assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0);
@@ -278,6 +280,17 @@ void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel features) {
         s->n_failed_attempts  = (unsigned) -1;
 }
 
+void dns_server_packet_rrsig_missing(DnsServer *s) {
+        _cleanup_free_ char *ip = NULL;
+        assert(s);
+        assert(s->manager);
+
+        in_addr_to_string(s->family, &s->address, &ip);
+        log_warning("DNS server %s does not augment replies with RRSIG records, DNSSEC not available.", strna(ip));
+
+        s->rrsig_missing = true;
+}
+
 static bool dns_server_grace_period_expired(DnsServer *s) {
         usec_t ts;
 
@@ -307,6 +320,7 @@ DnsServerFeatureLevel dns_server_possible_features(DnsServer *s) {
                 s->possible_features = DNS_SERVER_FEATURE_LEVEL_BEST;
                 s->n_failed_attempts = 0;
                 s->verified_usec = 0;
+                s->rrsig_missing = false;
 
                 in_addr_to_string(s->family, &s->address, &ip);
                 log_info("Grace period over, resuming full feature set for DNS server %s", strna(ip));
index b07fc3af3d00945416b2ae04f259eec57d3ab305..3011904bfdb1540fe5eafab316c4d94afed8e244 100644 (file)
@@ -61,8 +61,6 @@ struct DnsServer {
         int family;
         union in_addr_union address;
 
-        bool marked:1;
-
         usec_t resend_timeout;
         usec_t max_rtt;
 
@@ -73,6 +71,15 @@ struct DnsServer {
         usec_t verified_usec;
         usec_t features_grace_period_usec;
 
+        /* Indicates whether responses are augmented with RRSIG by
+         * server or not. Note that this is orthogonal to the feature
+         * level stuff, as it's only information describing responses,
+         * and has no effect on how the questions are asked. */
+        bool rrsig_missing:1;
+
+        /* Used when GC'ing old DNS servers when configuration changes. */
+        bool marked:1;
+
         /* If linked is set, then this server appears in the servers linked list */
         bool linked:1;
         LIST_FIELDS(DnsServer, servers);
@@ -95,6 +102,7 @@ void dns_server_move_back_and_unmark(DnsServer *s);
 void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, usec_t rtt, size_t size);
 void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t usec);
 void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel features);
+void dns_server_packet_rrsig_missing(DnsServer *s);
 
 DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr);
 
index 2875330812bee1aae8057ba001b9d8af0a1eb9ce..5933e0e462ca761291394104267cbed6b9059c7a 100644 (file)
@@ -478,10 +478,19 @@ static void dns_transaction_process_dnssec(DnsTransaction *t) {
                 return;
         }
 
+        if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER &&
+            t->scope->dnssec_mode == DNSSEC_YES) {
+                /*  We are not in automatic downgrade mode, and the
+                 *  server is bad, refuse operation. */
+                dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
+                return;
+        }
+
         if (!IN_SET(t->answer_dnssec_result,
-                    _DNSSEC_RESULT_INVALID, /* No DNSSEC validation enabled */
-                    DNSSEC_VALIDATED,       /* Answer is signed and validated successfully */
-                    DNSSEC_UNSIGNED)) {     /* Answer is right-fully unsigned */
+                    _DNSSEC_RESULT_INVALID,        /* No DNSSEC validation enabled */
+                    DNSSEC_VALIDATED,              /* Answer is signed and validated successfully */
+                    DNSSEC_UNSIGNED,               /* Answer is right-fully unsigned */
+                    DNSSEC_INCOMPATIBLE_SERVER)) { /* Server does not do DNSSEC (Yay, we are downgrade attack vulnerable!) */
                 dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
                 return;
         }
@@ -1001,7 +1010,7 @@ static int dns_transaction_make_packet(DnsTransaction *t) {
         if (t->sent)
                 return 0;
 
-        r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode == DNSSEC_YES);
+        r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode != DNSSEC_NO);
         if (r < 0)
                 return r;
 
@@ -1336,9 +1345,14 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
          * - For other queries with no matching response RRs, and no NSEC/NSEC3, the SOA RR
          */
 
-        if (t->scope->dnssec_mode != DNSSEC_YES)
+        if (t->scope->dnssec_mode == DNSSEC_NO)
                 return 0;
 
+        if (t->current_features < DNS_SERVER_FEATURE_LEVEL_DO)
+                return 0; /* Server doesn't do DNSSEC, there's no point in requesting any RRs then. */
+        if (t->server && t->server->rrsig_missing)
+                return 0; /* Server handles DNSSEC requests, but isn't augmenting responses with RRSIGs. No point in trying DNSSEC then. */
+
         DNS_ANSWER_FOREACH(rr, t->answer) {
 
                 if (dns_type_is_pseudo(rr->key->type))
@@ -1682,7 +1696,7 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *
         /* Checks if the RR we are looking for must be signed with an
          * RRSIG. This is used for positive responses. */
 
-        if (t->scope->dnssec_mode != DNSSEC_YES)
+        if (t->scope->dnssec_mode == DNSSEC_NO)
                 return false;
 
         if (dns_type_is_pseudo(rr->key->type))
@@ -1819,7 +1833,7 @@ static int dns_transaction_requires_nsec(DnsTransaction *t) {
         /* Checks if we need to insist on NSEC/NSEC3 RRs for proving
          * this negative reply */
 
-        if (t->scope->dnssec_mode != DNSSEC_YES)
+        if (t->scope->dnssec_mode == DNSSEC_NO)
                 return false;
 
         if (dns_type_is_pseudo(t->key->type))
@@ -1932,6 +1946,17 @@ static int dns_transaction_dnskey_authenticated(DnsTransaction *t, DnsResourceRe
         return found ? false : -ENXIO;
 }
 
+static int dns_transaction_known_signed(DnsTransaction *t, DnsResourceRecord *rr) {
+        assert(t);
+        assert(rr);
+
+        /* We know that the root domain is signed, hence if it appears
+         * not to be signed, there's a problem with the DNS server */
+
+        return rr->key->class == DNS_CLASS_IN &&
+                dns_name_is_root(DNS_RESOURCE_KEY_NAME(rr->key));
+}
+
 int dns_transaction_validate_dnssec(DnsTransaction *t) {
         _cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL;
         bool dnskeys_finalized = false;
@@ -1945,7 +1970,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
          * t->validated_keys, let's see which RRs we can now
          * authenticate with that. */
 
-        if (t->scope->dnssec_mode != DNSSEC_YES)
+        if (t->scope->dnssec_mode == DNSSEC_NO)
                 return 0;
 
         /* Already validated */
@@ -1963,6 +1988,13 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
         if (t->answer_source != DNS_TRANSACTION_NETWORK)
                 return 0;
 
+        if (t->current_features < DNS_SERVER_FEATURE_LEVEL_DO ||
+            (t->server && t->server->rrsig_missing)) {
+                /* The server does not support DNSSEC, or doesn't augment responses with RRSIGs. */
+                t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER;
+                return 0;
+        }
+
         log_debug("Validating response from transaction %" PRIu16 " (%s).", t->id, dns_transaction_key_string(t));
 
         /* First see if there are DNSKEYs we already known a validated DS for. */
@@ -2037,6 +2069,33 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
                                                 changed = true;
                                                 break;
                                         }
+
+                                        r = dns_transaction_known_signed(t, rr);
+                                        if (r < 0)
+                                                return r;
+                                        if (r > 0) {
+                                                /* This is an RR we know has to be signed. If it isn't this means
+                                                 * the server is not attaching RRSIGs, hence complain. */
+
+                                                dns_server_packet_rrsig_missing(t->server);
+
+                                                if (t->scope->dnssec_mode == DNSSEC_DOWNGRADE_OK) {
+
+                                                        /* Downgrading is OK? If so, just consider the information unsigned */
+
+                                                        r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0);
+                                                        if (r < 0)
+                                                                return r;
+
+                                                        t->scope->manager->n_dnssec_insecure++;
+                                                        changed = true;
+                                                        break;
+                                                }
+
+                                                /* Otherwise, fail */
+                                                t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER;
+                                                return 0;
+                                        }
                                 }
 
                                 if (IN_SET(result,