]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/resolve/resolved-dns-packet.c
resolved: add missing error code check when initializing DNS-over-TLS
[thirdparty/systemd.git] / src / resolve / resolved-dns-packet.c
index 70260b38b876a3bbd7c0c5f03363459619992b28..20ee8c9ca3d25a3a0b1402f6c68282070e8be980 100644 (file)
@@ -1,26 +1,14 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
 
-  Copyright 2014 Lennart Poettering
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
- ***/
+#if HAVE_GCRYPT
+#include <gcrypt.h>
+#endif
 
 #include "alloc-util.h"
 #include "dns-domain.h"
+#include "memory-util.h"
 #include "resolved-dns-packet.h"
+#include "set.h"
 #include "string-table.h"
 #include "strv.h"
 #include "unaligned.h"
@@ -62,10 +50,10 @@ int dns_packet_new(
         /* The caller may not check what is going to be truly allocated, so do not allow to
          * allocate a DNS packet bigger than DNS_PACKET_SIZE_MAX.
          */
-        if (min_alloc_dsize > DNS_PACKET_SIZE_MAX) {
-                log_error("Requested packet data size too big: %zu", min_alloc_dsize);
-                return -EFBIG;
-        }
+        if (min_alloc_dsize > DNS_PACKET_SIZE_MAX)
+                return log_error_errno(SYNTHETIC_ERRNO(EFBIG),
+                                       "Requested packet data size too big: %zu",
+                                       min_alloc_dsize);
 
         /* When dns_packet_new() is called with min_alloc_dsize == 0, allocate more than the
          * absolute minimum (which is the dns packet header size), to avoid
@@ -397,7 +385,7 @@ int dns_packet_append_blob(DnsPacket *p, const void *d, size_t l, size_t *start)
         if (r < 0)
                 return r;
 
-        memcpy(q, d, l);
+        memcpy_safe(q, d, l);
         return 0;
 }
 
@@ -549,7 +537,7 @@ int dns_packet_append_name(
                         }
                 }
 
-                r = dns_label_unescape(&name, label, sizeof(label));
+                r = dns_label_unescape(&name, label, sizeof label, 0);
                 if (r < 0)
                         goto fail;
 
@@ -752,13 +740,20 @@ int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, in
                 static const uint8_t rfc6975[] = {
 
                         0, 5, /* OPTION_CODE: DAU */
+#if HAVE_GCRYPT && GCRYPT_VERSION_NUMBER >= 0x010600
+                        0, 7, /* LIST_LENGTH */
+#else
                         0, 6, /* LIST_LENGTH */
+#endif
                         DNSSEC_ALGORITHM_RSASHA1,
                         DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1,
                         DNSSEC_ALGORITHM_RSASHA256,
                         DNSSEC_ALGORITHM_RSASHA512,
                         DNSSEC_ALGORITHM_ECDSAP256SHA256,
                         DNSSEC_ALGORITHM_ECDSAP384SHA384,
+#if HAVE_GCRYPT && GCRYPT_VERSION_NUMBER >= 0x010600
+                        DNSSEC_ALGORITHM_ED25519,
+#endif
 
                         0, 6, /* OPTION_CODE: DHU */
                         0, 3, /* LIST_LENGTH */
@@ -859,7 +854,9 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, const DnsAns
                 if (r < 0)
                         goto fail;
 
-                r = dns_packet_append_name(p, rr->srv.name, true, false, NULL);
+                /* RFC 2782 states "Unless and until permitted by future standards
+                 * action, name compression is not to be used for this field." */
+                r = dns_packet_append_name(p, rr->srv.name, false, false, NULL);
                 break;
 
         case DNS_TYPE_PTR:
@@ -1461,8 +1458,7 @@ int dns_packet_read_name(
         if (after_rindex != 0)
                 p->rindex= after_rindex;
 
-        *_ret = ret;
-        ret = NULL;
+        *_ret = TAKE_PTR(ret);
 
         if (start)
                 *start = rewinder.saved_rindex;
@@ -1953,7 +1949,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl
         case DNS_TYPE_NSEC: {
 
                 /*
-                 * RFC6762, section 18.14 explictly states mDNS should use name compression.
+                 * RFC6762, section 18.14 explicitly states mDNS should use name compression.
                  * This contradicts RFC3845, section 2.1.1
                  */
 
@@ -2073,8 +2069,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl
         if (p->rindex != offset + rdlength)
                 return -EBADMSG;
 
-        *ret = rr;
-        rr = NULL;
+        *ret = TAKE_PTR(rr);
 
         if (ret_cache_flush)
                 *ret_cache_flush = cache_flush;
@@ -2129,25 +2124,28 @@ static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) {
         return true;
 }
 
-int dns_packet_extract(DnsPacket *p) {
+static int dns_packet_extract_question(DnsPacket *p, DnsQuestion **ret_question) {
         _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
-        _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
-        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = {};
         unsigned n, i;
         int r;
 
-        if (p->extracted)
-                return 0;
-
-        INIT_REWINDER(rewinder, p);
-        dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE);
-
         n = DNS_PACKET_QDCOUNT(p);
         if (n > 0) {
                 question = dns_question_new(n);
                 if (!question)
                         return -ENOMEM;
 
+                _cleanup_set_free_ Set *keys = NULL; /* references to keys are kept by Question */
+
+                keys = set_new(&dns_resource_key_hash_ops);
+                if (!keys)
+                        return log_oom();
+
+                r = set_reserve(keys, n * 2); /* Higher multipliers give slightly higher efficiency through
+                                               * hash collisions, but the gains quickly drop of after 2. */
+                if (r < 0)
+                        return r;
+
                 for (i = 0; i < n; i++) {
                         _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
                         bool cache_flush;
@@ -2162,119 +2160,162 @@ int dns_packet_extract(DnsPacket *p) {
                         if (!dns_type_is_valid_query(key->type))
                                 return -EBADMSG;
 
-                        r = dns_question_add(question, key);
+                        r = set_put(keys, key);
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
+                                /* Already in the Question, let's skip */
+                                continue;
+
+                        r = dns_question_add_raw(question, key);
                         if (r < 0)
                                 return r;
                 }
         }
 
+        *ret_question = TAKE_PTR(question);
+
+        return 0;
+}
+
+static int dns_packet_extract_answer(DnsPacket *p, DnsAnswer **ret_answer) {
+        _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
+        unsigned n, i;
+        _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *previous = NULL;
+        bool bad_opt = false;
+        int r;
+
         n = DNS_PACKET_RRCOUNT(p);
-        if (n > 0) {
-                _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *previous = NULL;
-                bool bad_opt = false;
+        if (n == 0)
+                return 0;
 
-                answer = dns_answer_new(n);
-                if (!answer)
-                        return -ENOMEM;
+        answer = dns_answer_new(n);
+        if (!answer)
+                return -ENOMEM;
 
-                for (i = 0; i < n; i++) {
-                        _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
-                        bool cache_flush = false;
+        for (i = 0; i < n; i++) {
+                _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
+                bool cache_flush = false;
 
-                        r = dns_packet_read_rr(p, &rr, &cache_flush, NULL);
-                        if (r < 0)
-                                return r;
+                r = dns_packet_read_rr(p, &rr, &cache_flush, NULL);
+                if (r < 0)
+                        return r;
 
-                        /* Try to reduce memory usage a bit */
-                        if (previous)
-                                dns_resource_key_reduce(&rr->key, &previous->key);
+                /* Try to reduce memory usage a bit */
+                if (previous)
+                        dns_resource_key_reduce(&rr->key, &previous->key);
 
-                        if (rr->key->type == DNS_TYPE_OPT) {
-                                bool has_rfc6975;
+                if (rr->key->type == DNS_TYPE_OPT) {
+                        bool has_rfc6975;
 
-                                if (p->opt || bad_opt) {
-                                        /* Multiple OPT RRs? if so, let's ignore all, because there's something wrong
-                                         * with the server, and if one is valid we wouldn't know which one. */
-                                        log_debug("Multiple OPT RRs detected, ignoring all.");
-                                        bad_opt = true;
-                                        continue;
-                                }
+                        if (p->opt || bad_opt) {
+                                /* Multiple OPT RRs? if so, let's ignore all, because there's
+                                 * something wrong with the server, and if one is valid we wouldn't
+                                 * know which one. */
+                                log_debug("Multiple OPT RRs detected, ignoring all.");
+                                bad_opt = true;
+                                continue;
+                        }
 
-                                if (!dns_name_is_root(dns_resource_key_name(rr->key))) {
-                                        /* If the OPT RR is not owned by the root domain, then it is bad, let's ignore
-                                         * it. */
-                                        log_debug("OPT RR is not owned by root domain, ignoring.");
-                                        bad_opt = true;
-                                        continue;
-                                }
+                        if (!dns_name_is_root(dns_resource_key_name(rr->key))) {
+                                /* If the OPT RR is not owned by the root domain, then it is bad,
+                                 * let's ignore it. */
+                                log_debug("OPT RR is not owned by root domain, ignoring.");
+                                bad_opt = true;
+                                continue;
+                        }
 
-                                if (i < DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)) {
-                                        /* OPT RR is in the wrong section? Some Belkin routers do this. This is a hint
-                                         * the EDNS implementation is borked, like the Belkin one is, hence ignore
-                                         * it. */
-                                        log_debug("OPT RR in wrong section, ignoring.");
-                                        bad_opt = true;
-                                        continue;
+                        if (i < DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)) {
+                                /* OPT RR is in the wrong section? Some Belkin routers do this. This
+                                 * is a hint the EDNS implementation is borked, like the Belkin one
+                                 * is, hence ignore it. */
+                                log_debug("OPT RR in wrong section, ignoring.");
+                                bad_opt = true;
+                                continue;
+                        }
+
+                        if (!opt_is_good(rr, &has_rfc6975)) {
+                                log_debug("Malformed OPT RR, ignoring.");
+                                bad_opt = true;
+                                continue;
+                        }
+
+                        if (DNS_PACKET_QR(p)) {
+                                /* Additional checks for responses */
+
+                                if (!DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(rr)) {
+                                        /* If this is a reply and we don't know the EDNS version
+                                         * then something is weird... */
+                                        log_debug("EDNS version newer that our request, bad server.");
+                                        return -EBADMSG;
                                 }
 
-                                if (!opt_is_good(rr, &has_rfc6975)) {
-                                        log_debug("Malformed OPT RR, ignoring.");
+                                if (has_rfc6975) {
+                                        /* If the OPT RR contains RFC6975 algorithm data, then this
+                                         * is indication that the server just copied the OPT it got
+                                         * from us (which contained that data) back into the reply.
+                                         * If so, then it doesn't properly support EDNS, as RFC6975
+                                         * makes it very clear that the algorithm data should only
+                                         * be contained in questions, never in replies. Crappy
+                                         * Belkin routers copy the OPT data for example, hence let's
+                                         * detect this so that we downgrade early. */
+                                        log_debug("OPT RR contains RFC6975 data, ignoring.");
                                         bad_opt = true;
                                         continue;
                                 }
+                        }
 
-                                if (DNS_PACKET_QR(p)) {
-                                        /* Additional checks for responses */
-
-                                        if (!DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(rr)) {
-                                                /* If this is a reply and we don't know the EDNS version then something
-                                                 * is weird... */
-                                                log_debug("EDNS version newer that our request, bad server.");
-                                                return -EBADMSG;
-                                        }
-
-                                        if (has_rfc6975) {
-                                                /* If the OPT RR contains RFC6975 algorithm data, then this is indication that
-                                                 * the server just copied the OPT it got from us (which contained that data)
-                                                 * back into the reply. If so, then it doesn't properly support EDNS, as
-                                                 * RFC6975 makes it very clear that the algorithm data should only be contained
-                                                 * in questions, never in replies. Crappy Belkin routers copy the OPT data for
-                                                 * example, hence let's detect this so that we downgrade early. */
-                                                log_debug("OPT RR contained RFC6975 data, ignoring.");
-                                                bad_opt = true;
-                                                continue;
-                                        }
-                                }
+                        p->opt = dns_resource_record_ref(rr);
+                } else {
+                        /* According to RFC 4795, section 2.9. only the RRs from the Answer section
+                         * shall be cached. Hence mark only those RRs as cacheable by default, but
+                         * not the ones from the Additional or Authority sections. */
+                        DnsAnswerFlags flags =
+                                (i < DNS_PACKET_ANCOUNT(p) ? DNS_ANSWER_CACHEABLE : 0) |
+                                (p->protocol == DNS_PROTOCOL_MDNS && !cache_flush ? DNS_ANSWER_SHARED_OWNER : 0);
+
+                        r = dns_answer_add(answer, rr, p->ifindex, flags);
+                        if (r < 0)
+                                return r;
+                }
 
-                                p->opt = dns_resource_record_ref(rr);
-                        } else {
+                /* Remember this RR, so that we potentically can merge it's ->key object with the
+                 * next RR. Note that we only do this if we actually decided to keep the RR around.
+                 */
+                dns_resource_record_unref(previous);
+                previous = dns_resource_record_ref(rr);
+        }
 
-                                /* According to RFC 4795, section 2.9. only the RRs from the Answer section shall be
-                                 * cached. Hence mark only those RRs as cacheable by default, but not the ones from the
-                                 * Additional or Authority sections. */
+        if (bad_opt)
+                p->opt = dns_resource_record_unref(p->opt);
 
-                                r = dns_answer_add(answer, rr, p->ifindex,
-                                                   (i < DNS_PACKET_ANCOUNT(p) ? DNS_ANSWER_CACHEABLE : 0) |
-                                                   (p->protocol == DNS_PROTOCOL_MDNS && !cache_flush ? DNS_ANSWER_SHARED_OWNER : 0));
-                                if (r < 0)
-                                        return r;
-                        }
+        *ret_answer = TAKE_PTR(answer);
 
-                        /* Remember this RR, so that we potentically can merge it's ->key object with the next RR. Note
-                         * that we only do this if we actually decided to keep the RR around. */
-                        dns_resource_record_unref(previous);
-                        previous = dns_resource_record_ref(rr);
-                }
+        return 0;
+}
 
-                if (bad_opt)
-                        p->opt = dns_resource_record_unref(p->opt);
-        }
+int dns_packet_extract(DnsPacket *p) {
+        _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
+        _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
+        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = {};
+        int r;
+
+        if (p->extracted)
+                return 0;
+
+        INIT_REWINDER(rewinder, p);
+        dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE);
+
+        r = dns_packet_extract_question(p, &question);
+        if (r < 0)
+                return r;
 
-        p->question = question;
-        question = NULL;
+        r = dns_packet_extract_answer(p, &answer);
+        if (r < 0)
+                return r;
 
-        p->answer = answer;
-        answer = NULL;
+        p->question = TAKE_PTR(question);
+        p->answer = TAKE_PTR(answer);
 
         p->extracted = true;
 
@@ -2309,6 +2350,25 @@ int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key) {
         return dns_resource_key_equal(p->question->keys[0], key);
 }
 
+static void dns_packet_hash_func(const DnsPacket *s, struct siphash *state) {
+        assert(s);
+
+        siphash24_compress(&s->size, sizeof(s->size), state);
+        siphash24_compress(DNS_PACKET_DATA((DnsPacket*) s), s->size, state);
+}
+
+static int dns_packet_compare_func(const DnsPacket *x, const DnsPacket *y) {
+        int r;
+
+        r = CMP(x->size, y->size);
+        if (r != 0)
+                return r;
+
+        return memcmp(DNS_PACKET_DATA((DnsPacket*) x), DNS_PACKET_DATA((DnsPacket*) y), x->size);
+}
+
+DEFINE_HASH_OPS(dns_packet_hash_ops, DnsPacket, dns_packet_hash_func, dns_packet_compare_func);
+
 static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = {
         [DNS_RCODE_SUCCESS] = "SUCCESS",
         [DNS_RCODE_FORMERR] = "FORMERR",