]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Parse the URI template and check for a dns variable
authorMark Andrews <marka@isc.org>
Mon, 9 Sep 2024 05:59:30 +0000 (15:59 +1000)
committerMark Andrews <marka@isc.org>
Tue, 26 Nov 2024 01:38:49 +0000 (12:38 +1100)
The 'dns' variable in dohpath can be in various forms ({?dns},
{dns}, {&dns} etc.).  To check for a valid dohpath it ends up
being simpler to just parse the URI template rather than looking
for all the various forms if substring.

lib/dns/rdata.c
lib/dns/rdata/in_1/svcb_64.c
tests/dns/rdata_test.c

index db7351a0cdf1cf901fc118418403e3e54a9b1d5c..976b3c6d9c9ff175d2469abff337089e04fabf46 100644 (file)
@@ -28,6 +28,7 @@
 #include <isc/parseint.h>
 #include <isc/result.h>
 #include <isc/string.h>
+#include <isc/utf8.h>
 #include <isc/util.h>
 
 #include <dns/callbacks.h>
@@ -636,6 +637,176 @@ check_private(isc_buffer_t *source, dns_secalg_t alg) {
        return ISC_R_SUCCESS;
 }
 
+/*
+ * A relative URI template that has a "dns" variable.
+ */
+static bool
+validate_dohpath(isc_region_t *region) {
+       const unsigned char *p;
+       const unsigned char *v = NULL;
+       const unsigned char *n = NULL;
+       unsigned char c;
+       bool dns = false;
+       bool wasop = false;
+       enum {
+               path,
+               variable,
+               percent1,
+               percent2,
+               variable_percent1,
+               variable_percent2,
+               prefix,
+               explode
+       } state = path;
+
+       if (region->length == 0 || *region->base != '/' ||
+           !isc_utf8_valid(region->base, region->length))
+       {
+               return false;
+       }
+
+       /*
+        * RFC 6570 URI Template check + "dns" variable.
+        */
+       p = region->base;
+       while (p < region->base + region->length) {
+               switch (state) {
+               case path:
+                       switch (*p++) {
+                       case '{': /*}*/
+                               state = variable;
+                               wasop = false;
+                               v = p;
+                               break;
+                       case '%':
+                               state = percent1;
+                               break;
+                       default:
+                               break;
+                       }
+                       break;
+               case variable:
+                       c = *p++;
+                       switch (c) {
+                       case '+':
+                       case '#':
+                       case '.':
+                       case '/':
+                       case ';':
+                       case '?':
+                       case '&':
+                               /* Operators. */
+                               if (p != v + 1 || wasop) {
+                                       return false;
+                               }
+                               wasop = true;
+                               v = p;
+                               break;
+                       case '=':
+                       case '!':
+                       case '@':
+                       case '|':
+                               /* Reserved operators. */
+                               return false;
+                       case '*':
+                       case ':':
+                       case '}':
+                       case ',':
+                               /* Found the end of the variable name. */
+                               if (p == (v + 1)) {
+                                       return false;
+                               }
+                               /* 'p' has been incremented so 4 not 3 */
+                               if ((p - v) == 4 && memcmp(v, "dns", 3) == 0) {
+                                       dns = true;
+                               }
+                               switch (c) {
+                               case ':':
+                                       state = prefix;
+                                       n = p;
+                                       break;
+                               case /*{*/ '}':
+                                       state = path;
+                                       break;
+                               case '*':
+                                       state = explode;
+                                       break;
+                               case ',':
+                                       wasop = false;
+                                       v = p;
+                                       break;
+                               }
+                               break;
+                       case '%':
+                               /* Percent encoded variable name. */
+                               state = variable_percent1;
+                               break;
+                       default:
+                               /* Valid variable name character? */
+                               if (c != '_' && !isalnum(c)) {
+                                       return false;
+                               }
+                               break;
+                       }
+                       break;
+               case explode:
+                       switch (*p++) {
+                       case ',':
+                               state = variable;
+                               wasop = false;
+                               v = p;
+                               break;
+                       case /*}*/ '}':
+                               state = path;
+                               break;
+                       default:
+                               return false;
+                       }
+                       break;
+               /* Check % encoding */
+               case percent1:
+               case percent2:
+               case variable_percent1:
+               case variable_percent2:
+                       /* bad percent encoding? */
+                       if (!isxdigit(*p++)) {
+                               return false;
+                       }
+                       if (state == percent1) {
+                               state = percent2;
+                       } else if (state == percent2) {
+                               state = path;
+                       } else if (state == variable_percent1) {
+                               state = variable_percent2;
+                       } else {
+                               state = variable;
+                       }
+                       break;
+               case prefix:
+                       c = *p++;
+                       if (!isdigit(c)) {
+                               /* valid number range [1..9999] */
+                               if ((p == n + 1) || (p - n) > 5 || *n == '0') {
+                                       return false;
+                               }
+                               switch (c) {
+                               case ',':
+                                       state = variable;
+                                       wasop = false;
+                                       break;
+                               case /*{*/ '}':
+                                       state = path;
+                                       break;
+                               default:
+                                       return false;
+                               }
+                       }
+                       break;
+               }
+       }
+       return state == path && dns;
+}
+
 #include "code.h"
 
 #define META    0x0001
index 754e3ef71f8d400c567edd530388185636bee479..8764617688fcf943bd0218e8f5c1aa7f33fead29 100644 (file)
@@ -155,30 +155,7 @@ svcb_validate(uint16_t key, isc_region_t *region) {
                        case sbpr_base64:
                                break;
                        case sbpr_dohpath:
-                               /*
-                                * Minimum valid dohpath is "/{?dns}" as
-                                * it MUST be relative (leading "/") and
-                                * MUST contain "{?dns}" or "{&dns}".
-                                */
-                               if (region->length < 7) {
-                                       return DNS_R_FORMERR;
-                               }
-                               /* MUST be relative */
-                               if (region->base[0] != '/') {
-                                       return DNS_R_FORMERR;
-                               }
-                               /* MUST be UTF8 */
-                               if (!isc_utf8_valid(region->base,
-                                                   region->length))
-                               {
-                                       return DNS_R_FORMERR;
-                               }
-                               /* MUST contain "{?dns}" or "{&dns}" */
-                               if (strnstr((char *)region->base, "{?dns}",
-                                           region->length) == NULL &&
-                                   strnstr((char *)region->base, "{&dns}",
-                                           region->length) == NULL)
-                               {
+                               if (!validate_dohpath(region)) {
                                        return DNS_R_FORMERR;
                                }
                                break;
index de7a6b44e89c4bf4b0ad9d59a72b2013ea8f10d2..bed419a20e4b93848b608b77297e3463daf92038 100644 (file)
@@ -2697,15 +2697,50 @@ ISC_RUN_TEST_IMPL(https_svcb) {
                TEXT_INVALID("1 foo.example.com. ( mandatory=key123,key123 "
                             "key123=abc)"),
                /* dohpath tests */
+               TEXT_VALID_LOOPCHG(1, "1 example.net. dohpath=/{dns}",
+                                  "1 example.net. key7=\"/{dns}\""),
+               TEXT_VALID_LOOPCHG(1, "1 example.net. dohpath=/{+dns}",
+                                  "1 example.net. key7=\"/{+dns}\""),
+               TEXT_VALID_LOOPCHG(1, "1 example.net. dohpath=/{#dns}",
+                                  "1 example.net. key7=\"/{#dns}\""),
+               TEXT_VALID_LOOPCHG(1, "1 example.net. dohpath=/{.dns}",
+                                  "1 example.net. key7=\"/{.dns}\""),
+               TEXT_VALID_LOOPCHG(1, "1 example.net. dohpath=\"/{;dns}\"",
+                                  "1 example.net. key7=\"/{;dns}\""),
                TEXT_VALID_LOOPCHG(1, "1 example.net. dohpath=/{?dns}",
                                   "1 example.net. key7=\"/{?dns}\""),
                TEXT_VALID_LOOPCHG(1, "1 example.net. dohpath=/some/path{?dns}",
                                   "1 example.net. key7=\"/some/path{?dns}\""),
-               TEXT_VALID_LOOPCHG(1, "1 example.net. dohpath=/some/path?key=value{&dns}",
-                                  "1 example.net. key7=\"/some/path?key=value{&dns}\""),
-               TEXT_INVALID("1 example.com. dohpath=no-slash"),
-               TEXT_INVALID("1 example.com. dohpath=/{?notdns}"),
-               TEXT_INVALID("1 example.com. dohpath=/notvariable"),
+               TEXT_VALID_LOOPCHG(1, "1 example.net. dohpath=/{dns:9999}",
+                                  "1 example.net. key7=\"/{dns:9999}\""),
+               TEXT_VALID_LOOPCHG(1, "1 example.net. dohpath=/{dns*}",
+                                  "1 example.net. key7=\"/{dns*}\""),
+               TEXT_VALID_LOOPCHG(
+                       1, "1 example.net. dohpath=/some/path?key=value{&dns}",
+                       "1 example.net. key7=\"/some/path?key=value{&dns}\""),
+               TEXT_VALID_LOOPCHG(1,
+                                  "1 example.net. "
+                                  "dohpath=/some/path?key=value{&dns,x*}",
+                                  "1 example.net. "
+                                  "key7=\"/some/path?key=value{&dns,x*}\""),
+               TEXT_INVALID("1 example.com. dohpath=not-relative"),
+               TEXT_INVALID("1 example.com. dohpath=/{?no_dns_variable}"),
+               TEXT_INVALID("1 example.com. dohpath=/novariable"),
+               TEXT_INVALID("1 example.com. dohpath=/{?dnsx}"),
+               /* index too big > 9999 */
+               TEXT_INVALID("1 example.com. dohpath=/{?dns:10000}"),
+               /* index not postive */
+               TEXT_INVALID("1 example.com. dohpath=/{?dns:0}"),
+               /* index leading zero */
+               TEXT_INVALID("1 example.com. dohpath=/{?dns:01}"),
+               /* two operators */
+               TEXT_INVALID("1 example.com. dohpath=/{??dns}"),
+               /* invalid % encoding */
+               TEXT_INVALID("1 example.com. dohpath=/%a{?dns}"),
+               /* invalid % encoding */
+               TEXT_INVALID("1 example.com. dohpath=/{?dns,%a}"),
+               /* incomplete macro */
+               TEXT_INVALID("1 example.com. dohpath=/{?dns" /*}*/),
                TEXT_SENTINEL()
 
        };