]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
compress DNS labels recursively.
authorAlan T. DeKok <aland@freeradius.org>
Mon, 21 Oct 2019 19:36:14 +0000 (15:36 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Mon, 21 Oct 2019 19:36:14 +0000 (15:36 -0400)
Which ends up being O(N+M) instead of O(N^2)

src/lib/util/value.c

index 748ec6b7ad23ec64e7b8a0e45d0f67bf0775a4ec..ac88b853d4f03924f175b1e6cc1f9eccadc39a34 100644 (file)
@@ -1296,117 +1296,224 @@ ssize_t fr_value_box_to_network(size_t *need, uint8_t *dst, size_t dst_len, fr_v
        return min;
 }
 
-/** Match two DNS labels
+/** Compress "label" by looking at it recursively.
  *
- *  Both 'a' and 'b' MUST start with a length byte.
- *
- *  Both 'a' and 'b' MUST be valid labels.  This function does NOT
- *  validate the input at all!
- *
- *  'a' MAY contain compressed pointers.  'b' MUST NOT contain
- *  compressed pointers.
  */
-static bool dns_label_match(uint8_t const *start, uint8_t const *a, uint8_t const *b)
+static bool dns_label_compress(uint8_t const *start, uint8_t const *end, uint8_t *label, uint8_t **new_end)
 {
-       /*
-        *      Lengths don't match, we're done.
-        */
-       if (*a != *b) return false;
+       uint8_t *next;
+       uint8_t const *q, *ptr, *suffix;
+       uint16_t offset;
 
        /*
-        *      Prefixes don't match, we're done.
+        *      Don't compress "end of label" byte or pointers.
         */
-       if (strncasecmp((char const *) a + 1, (char const *) b + 1, *a) != 0) return false;
+       if (!*label || (*label > 63)) {
+               return false;
+       }
 
        /*
-        *      Skip the matching label.
+        *      Check the next label.  Note that this is *after*
+        *      "end".  It also MUST be a valid, uncompressed label.
         */
-       b += *a + 1;
-       a += *a + 1;
+       next = label + *label + 1;
 
        /*
-        *      If both labels are done, then it's a match.
+        *      We're at the last uncompressed label, scan the input
+        *      buffer to see if there's a match.
+        *
+        *      The scan skips ahead until it find both a label length
+        *      that matches, AND "next" label which is 0x00.  Only
+        *      then does it do the string compare of the label
+        *      values.
         */
-       if (!*a && !*b) return true;
+       if (*next == 0x00) {
+               q = start;
+               while (q < end) {
+                       if (*q == 0x00) {
+                               q++;
+                               continue;
+                       }
 
-       /*
-        *      If 'a' is now a compressed pointer, jump to the actual
-        *      label.  Note that we don't do bounds checking on the
-        *      offset.  It's always positive, and it's always valid.
-        */
-       if (*a > 63) {
-               uint16_t offset;
+                       if (*q > 63) {
+                               q += 2;
+                               continue;
+                       }
 
-               offset = a[1];
-               offset += ((*a & ~0xc0) << 8);
+                       ptr = q + *q + 1;
+                       if (ptr > end) return false;
 
-               a = start + offset;
+                       /*
+                        *      Label lengths aren't the same, skip
+                        *      it.
+                        */
+                       if (*q != *label) {
+                               q = ptr;
+                               continue;
+                       }
+
+                       /*
+                        *      Out label ends with 0x00.  If this
+                        *      label doesn't end with 0x00, skip it.
+                        */
+                       if (*ptr != 0x00) {
+                               q = ptr;
+                               continue;
+                       }
+
+                       /*
+                        *      The pointer is too far away.  Don't
+                        *      point to it.  This check is mainly for
+                        *      static analyzers.
+                        */
+                       if ((q - start) > (1 << 14)) return false;
+
+                       /*
+                        *      Only now do case-insensitive
+                        *      comparisons.
+                        */
+                       if (strncasecmp((char const *) q + 1, (char const *) label +1, *label) != 0) {
+                               q = ptr;
+                               continue;
+                       }
+
+                       /*
+                        *      All of that matches.  Replace the
+                        *      label with a compressed pointer.  And
+                        *      return that we managed to compress
+                        *      this label.
+                        */
+                       offset = (q - start);
+                       label[0] = (offset >> 8) | 0xc0;
+                       label[1] = offset & 0xff;
+                       *new_end = label + 2;
+                       return true;
+               }
+
+               return false;
+       }
+
+       /*
+        *      The next label is still uncompressed, so we call
+        *      ourselves recursively in order to compress it.
+        */
+       if (*next < 63) {
+               if (!dns_label_compress(start, end, next, new_end)) return false;
 
                /*
-                *      Compressed pointers can't point to compressed
-                *      pointers.
+                *      Else it WAS compressed.
                 */
-               if (*a > 63) return false;
        }
 
-       return dns_label_match(start, a, b);
-}
+       /*
+        *      The next label wasn't compressed, OR it is invalid,
+        *      skip it.  This check is here only to shut up the
+        *      static analysis tools.
+        */
+       if (*next < 0xc0) {
+               return false;
+       }
 
-/** Compress "label" by looking for other labels in start..end
- *
- *  We assume that the input is valid.  If it isn't valid, bad things
- *  will happen.
- *
- * "label" MUST be uncompressed.
- */
-static bool dns_label_compress(uint8_t const *start, uint8_t const *end, uint8_t *label)
-{
-       uint8_t const *p;
-       uint8_t *q;
-       size_t offset;
+       /*
+        *      Remember where our suffix points to.
+        */
+       suffix = start + ((next[0] & ~0xc0) << 8) + next[1];
 
-       p = start;
-       q = label;
+       /*
+        *      Our label now ends with a compressed pointer.  Scan
+        *      the input until we find either an uncompressed label
+        *      which ends with the same compressed pointer, OR we
+        *      find an uncompressed label which ends AT our
+        *      compressed pointer.
+        */
+       q = start;
+       while (q < end) {
+               if (*q == 0x00) {
+                       q++;
+                       continue;
+               }
 
-       while (p < end) {
                /*
-                *      Skip compressed pointers.  We know they don't
-                *      match.
+                *      Skip compressed pointers.  We can't point to
+                *      compressed pointers/
                 */
-               if (*p > 63) {
-                       p += 2;
+               if (*q > 63) {
+                       q += 2;
                        continue;
                }
 
                /*
-                *      Skip end bytes, which clearly don't match.
+                *      We now have an uncompressed label in the input
+                *      buffer.  Check for a match.
                 */
-               if (*p == 0) {
-                       p++;
+               ptr = q + *q + 1;
+               if (ptr > end) return false;
+
+               /*
+                *      Label lengths aren't the same, skip it.
+                */
+               if (*q != *label) {
+                       q = ptr;
                        continue;
                }
 
                /*
-                *      We know have something which MIGHT match.  Do
-                *      a full check on it, including dereferencing
-                *      compressed pointers.
+                *      If the NEXT label is uncompressed, then skip
+                *      it unless it's the suffix we're pointing to.
                 */
-               if (!dns_label_match(start, p, q)) {
-                       p += *p + 1;
+               if (*ptr < 63) {
+                       if (ptr != suffix) {
+                               q = ptr;
+                               continue;
+                       }
+
+                       goto check_label;
+               }
+
+               /*
+                *      The next label is a compressed pointer.  If
+                *      the compressed pointers are different, then
+                *      skip both this label and the compressed
+                *      pointer after it.
+                */
+               if ((ptr[0] != next[0]) ||
+                   (ptr[1] != next[1])) {
+                       q = ptr + 2;
                        continue;
                }
 
+       check_label:
                /*
-                *      We do have a match, encode the offset.
+                *      Pointer is too far away.  Don't point
+                *      to it.
                 */
-               offset = p - start;
-               if (offset > (1 << 14)) return false;
+               if ((q - start) > (1 << 14)) return false;
 
+               /*
+                *      Only now do case-insensitive
+                *      comparisons.
+                */
+               if (strncasecmp((char const *) q + 1, (char const *) label +1, *label) != 0) {
+                       q = ptr;
+                       continue;
+               }
+
+               /*
+                *      All of that matches.  Replace the
+                *      label with a compressed pointer.  And
+                *      return that we managed to compress
+                *      this label.
+                */
+               offset = (q - start);
+               label[0] = (offset >> 8) | 0xc0;
                label[1] = offset & 0xff;
-               label[0] = ((offset >> 8) & 0xff) | 0xc0;
+               *new_end = label + 2;
                return true;
        }
 
+       /*
+        *      Who knows what it is, we couldn't compress it.
+        */
        return false;
 }
 
@@ -1577,20 +1684,9 @@ ssize_t fr_value_box_to_dns_label(size_t *need, uint8_t *buf, size_t buf_len, ui
        if (!compression || (buf == where) || ((data - where) <= 2)) goto done;
 
        /*
-        *      Do label compression on the resulting string, starting
-        *      from the beginning of the input buffer, and ending
-        *      where we started encoding this label.
-        *
-        *      @todo - this is pretty brute-force.  It works for
-        *      small number of labels and small buffers.  It's
-        *      inefficient for anything large.
+        *      Compress it, AND tell us where the new end buffer is located.
         */
-       for (label = where; *label != 0; label += *label + 1) {
-               if (dns_label_compress(buf, where, label)) {
-                       data = label + 2;
-                       break;
-               }
-       }
+       (void) dns_label_compress(buf, where, where, &data);
 
 done:
        if (need) *need = 0;