]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dns-domain: be more strict when encoding/decoding labels
authorLennart Poettering <lennart@poettering.net>
Sun, 29 Nov 2015 13:12:05 +0000 (14:12 +0100)
committerLennart Poettering <lennart@poettering.net>
Mon, 30 Nov 2015 18:37:41 +0000 (19:37 +0100)
Labels of zero length are not OK, refuse them early on. The concept of a
"zero-length label" doesn't exist, a zero-length full domain name
however does (representing the root domain). See RFC 2181, Section 11.

src/shared/dns-domain.c
src/shared/dns-domain.h
src/test/test-dns-domain.c

index 09ec6e70a0f99b6c12f2bef92ab03fbfe35a7883..7a4093cc474236f6c0a79b66a4a3bc7aa0304dd6 100644 (file)
@@ -185,7 +185,11 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha
 int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
         char *q;
 
-        if (l > DNS_LABEL_MAX)
+        /* DNS labels must be between 1 and 63 characters long. A
+         * zero-length label does not exist. See RFC 2182, Section
+         * 11. */
+
+        if (l <= 0 || l > DNS_LABEL_MAX)
                 return -EINVAL;
         if (sz < 1)
                 return -ENOSPC;
@@ -198,10 +202,11 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
 
                 if (*p == '.' || *p == '\\') {
 
+                        /* Dot or backslash */
+
                         if (sz < 3)
                                 return -ENOSPC;
 
-                        /* Dot or backslash */
                         *(q++) = '\\';
                         *(q++) = *p;
 
@@ -253,7 +258,7 @@ int dns_label_escape_new(const char *p, size_t l, char **ret) {
         assert(p);
         assert(ret);
 
-        if (l > DNS_LABEL_MAX)
+        if (l <= 0 || l > DNS_LABEL_MAX)
                 return -EINVAL;
 
         s = new(char, DNS_LABEL_ESCAPED_MAX);
@@ -273,32 +278,52 @@ int dns_label_escape_new(const char *p, size_t l, char **ret) {
 int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
 #ifdef HAVE_LIBIDN
         _cleanup_free_ uint32_t *input = NULL;
-        size_t input_size;
+        size_t input_size, l;
         const char *p;
         bool contains_8bit = false;
+        char buffer[DNS_LABEL_MAX+1];
 
         assert(encoded);
         assert(decoded);
-        assert(decoded_max >= DNS_LABEL_MAX);
+
+        /* Converts an U-label into an A-label */
 
         if (encoded_size <= 0)
-                return 0;
+                return -EINVAL;
 
         for (p = encoded; p < encoded + encoded_size; p++)
                 if ((uint8_t) *p > 127)
                         contains_8bit = true;
 
-        if (!contains_8bit)
+        if (!contains_8bit) {
+                if (encoded_size > DNS_LABEL_MAX)
+                        return -EINVAL;
+
                 return 0;
+        }
 
         input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
         if (!input)
                 return -ENOMEM;
 
-        if (idna_to_ascii_4i(input, input_size, decoded, 0) != 0)
+        if (idna_to_ascii_4i(input, input_size, buffer, 0) != 0)
+                return -EINVAL;
+
+        l = strlen(buffer);
+
+        /* Verify that the the result is not longer than one DNS label. */
+        if (l <= 0 || l > DNS_LABEL_MAX)
                 return -EINVAL;
+        if (l > decoded_max)
+                return -ENOSPC;
+
+        memcpy(decoded, buffer, l);
 
-        return strlen(decoded);
+        /* If there's room, append a trailing NUL byte, but only then */
+        if (decoded_max > l)
+                decoded[l] = 0;
+
+        return (int) l;
 #else
         return 0;
 #endif
@@ -312,11 +337,14 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded,
         uint32_t *output = NULL;
         size_t w;
 
-        /* To be invoked after unescaping */
+        /* To be invoked after unescaping. Converts an A-label into an U-label. */
 
         assert(encoded);
         assert(decoded);
 
+        if (encoded_size <= 0 || encoded_size > DNS_LABEL_MAX)
+                return -EINVAL;
+
         if (encoded_size < sizeof(IDNA_ACE_PREFIX)-1)
                 return 0;
 
@@ -336,11 +364,16 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded,
         if (!result)
                 return -ENOMEM;
         if (w <= 0)
-                return 0;
-        if (w+1 > decoded_max)
                 return -EINVAL;
+        if (w > decoded_max)
+                return -ENOSPC;
+
+        memcpy(decoded, result, w);
+
+        /* Append trailing NUL byte if there's space, but only then. */
+        if (decoded_max > w)
+                decoded[w] = 0;
 
-        memcpy(decoded, result, w+1);
         return w;
 #else
         return 0;
@@ -511,24 +544,32 @@ int dns_name_equal(const char *x, const char *y) {
                 r = dns_label_unescape(&x, la, sizeof(la));
                 if (r < 0)
                         return r;
-
-                k = dns_label_undo_idna(la, r, la, sizeof(la));
-                if (k < 0)
-                        return k;
-                if (k > 0)
-                        r = k;
+                if (r > 0) {
+                        k = dns_label_undo_idna(la, r, la, sizeof(la));
+                        if (k < 0)
+                                return k;
+                        if (k > 0)
+                                r = k;
+                }
 
                 q = dns_label_unescape(&y, lb, sizeof(lb));
                 if (q < 0)
                         return q;
-                w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
-                if (w < 0)
-                        return w;
-                if (w > 0)
-                        q = w;
+                if (q > 0) {
+                        w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
+                        if (w < 0)
+                                return w;
+                        if (w > 0)
+                                q = w;
+                }
+
+                /* If one name had fewer labels than the other, this
+                 * will show up as empty label here, which the
+                 * strcasecmp() below will properly consider different
+                 * from a non-empty label. */
 
                 la[r] = lb[q] = 0;
-                if (strcasecmp(la, lb))
+                if (strcasecmp(la, lb) != 0)
                         return false;
         }
 }
@@ -549,11 +590,13 @@ int dns_name_endswith(const char *name, const char *suffix) {
                 r = dns_label_unescape(&n, ln, sizeof(ln));
                 if (r < 0)
                         return r;
-                k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
-                if (k < 0)
-                        return k;
-                if (k > 0)
-                        r = k;
+                if (r > 0) {
+                        k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
+                        if (k < 0)
+                                return k;
+                        if (k > 0)
+                                r = k;
+                }
 
                 if (!saved_n)
                         saved_n = n;
@@ -561,11 +604,13 @@ int dns_name_endswith(const char *name, const char *suffix) {
                 q = dns_label_unescape(&s, ls, sizeof(ls));
                 if (q < 0)
                         return q;
-                w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
-                if (w < 0)
-                        return w;
-                if (w > 0)
-                        q = w;
+                if (q > 0) {
+                        w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
+                        if (w < 0)
+                                return w;
+                        if (w > 0)
+                                q = w;
+                }
 
                 if (r == 0 && q == 0)
                         return true;
@@ -605,11 +650,13 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
                 r = dns_label_unescape(&n, ln, sizeof(ln));
                 if (r < 0)
                         return r;
-                k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
-                if (k < 0)
-                        return k;
-                if (k > 0)
-                        r = k;
+                if (r > 0) {
+                        k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
+                        if (k < 0)
+                                return k;
+                        if (k > 0)
+                                r = k;
+                }
 
                 if (!saved_after)
                         saved_after = n;
@@ -617,11 +664,13 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
                 q = dns_label_unescape(&s, ls, sizeof(ls));
                 if (q < 0)
                         return q;
-                w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
-                if (w < 0)
-                        return w;
-                if (w > 0)
-                        q = w;
+                if (q > 0) {
+                        w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
+                        if (w < 0)
+                                return w;
+                        if (w > 0)
+                                q = w;
+                }
 
                 if (r == 0 && q == 0)
                         break;
index 99c72574dbb9ef8bfbe4e11ce88c0eec50f7f9e9..c68f1945e1f5cf208bfb7290753c5f94387c468a 100644 (file)
 #include "hashmap.h"
 #include "in-addr-util.h"
 
+/* Length of a single label, with all escaping removed, excluding any trailing dot or NUL byte */
 #define DNS_LABEL_MAX 63
+
+/* Worst case length of a single label, with all escaping applied and room for a trailing NUL byte. */
 #define DNS_LABEL_ESCAPED_MAX (DNS_LABEL_MAX*4+1)
 
 int dns_label_unescape(const char **name, char *dest, size_t sz);
index f010e4e19ac4a26ba4e46e5c4b427bf61821dbda..7ad59d378a5f5999c932f5f2e53eb2efd9c7eb4a 100644 (file)
@@ -136,7 +136,7 @@ static void test_dns_label_escape_one(const char *what, size_t l, const char *ex
 }
 
 static void test_dns_label_escape(void) {
-        test_dns_label_escape_one("", 0, "", 0);
+        test_dns_label_escape_one("", 0, NULL, -EINVAL);
         test_dns_label_escape_one("hallo", 5, "hallo", 5);
         test_dns_label_escape_one("hallo", 6, NULL, -EINVAL);
         test_dns_label_escape_one("hallo hallo.foobar,waldi", 24, "hallo\\032hallo\\.foobar\\044waldi", 31);