]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
validator: fix NSEC* + delegation -> NXDOMAIN
authorVladimír Čunát <vladimir.cunat@nic.cz>
Fri, 5 Jan 2018 09:15:43 +0000 (10:15 +0100)
committerVladimír Čunát <vladimir.cunat@nic.cz>
Mon, 22 Jan 2018 10:17:44 +0000 (11:17 +0100)
lib/dnssec/nsec.c
lib/dnssec/nsec.h
lib/dnssec/nsec3.c

index 52bfb94300580cb8cae3ca31a964d3e640e4dc12..0b9193f076c9814f86f7c70e6f72c678b9171e8f 100644 (file)
@@ -30,6 +30,7 @@
 bool kr_nsec_bitmap_contains_type(const uint8_t *bm, uint16_t bm_size, uint16_t type)
 {
        if (!bm || bm_size == 0) {
+               assert(bm);
                return false;
        }
 
@@ -59,29 +60,52 @@ bool kr_nsec_bitmap_contains_type(const uint8_t *bm, uint16_t bm_size, uint16_t
        return false;
 }
 
+int kr_nsec_children_in_zone_check(const uint8_t *bm, uint16_t bm_size)
+{
+       if (!bm) {
+               return kr_error(EINVAL);
+       }
+       const bool parent_side =
+               kr_nsec_bitmap_contains_type(bm, bm_size, KNOT_RRTYPE_DNAME)
+               || (kr_nsec_bitmap_contains_type(bm, bm_size, KNOT_RRTYPE_NS)
+                   && !kr_nsec_bitmap_contains_type(bm, bm_size, KNOT_RRTYPE_SOA)
+               );
+       return parent_side ? abs(ENOENT) : kr_ok();
+       /* LATER: after refactoring, probably also check if signer name equals owner,
+        * but even without that it's not possible to attack *correctly* signed zones.
+        */
+}
+
 /**
  * Check whether the NSEC RR proves that there is no closer match for <SNAME, SCLASS>.
  * @param nsec  NSEC RRSet.
  * @param sname Searched name.
- * @return      0 or error code.
+ * @return      0 if proves, >0 if not (abs(ENOENT)), or error code (<0).
  */
-static int nsec_nonamematch(const knot_rrset_t *nsec, const knot_dname_t *sname)
+static int nsec_covers(const knot_rrset_t *nsec, const knot_dname_t *sname)
 {
        assert(nsec && sname);
        const knot_dname_t *next = knot_nsec_next(&nsec->rrs);
+       if (knot_dname_cmp(sname, nsec->owner) <= 0) {
+               return abs(ENOENT); /* 'sname' before 'owner', so can't be covered */
+       }
        /* If NSEC 'owner' >= 'next', it means that there is nothing after 'owner' */
-       const bool is_last_nsec = (knot_dname_cmp(nsec->owner, next) >= 0);
-       if (is_last_nsec) { /* SNAME is after owner => provably doesn't exist */
-               if (knot_dname_cmp(nsec->owner, sname) < 0) {
-                       return kr_ok();
-               }
-       } else {
-               /* Prove that SNAME is between 'owner' and 'next' */
-               if ((knot_dname_cmp(nsec->owner, sname) < 0) && (knot_dname_cmp(sname, next) < 0)) {
-                       return kr_ok();
-               }
+       const bool is_last_nsec = knot_dname_cmp(nsec->owner, next) >= 0;
+       const bool in_range = is_last_nsec || knot_dname_cmp(sname, next) < 0;
+       if (!in_range) {
+               return abs(ENOENT);
        }
-       return kr_error(EINVAL);
+       /* Before returning kr_ok(), we have to check a special case:
+        * sname might be under delegation from owner and thus
+        * not in the zone of this NSEC at all.
+        */
+       if (!knot_dname_is_sub(sname, nsec->owner)) {
+               return kr_ok();
+       }
+       uint8_t *bm = NULL;
+       uint16_t bm_size = 0;
+       knot_nsec_bitmap(&nsec->rrs, &bm, &bm_size);
+       return kr_nsec_children_in_zone_check(bm, bm_size);
 }
 
 #define FLG_NOEXIST_RRTYPE (1 << 0) /**< <SNAME, SCLASS> exists, <SNAME, SCLASS, STYPE> does not exist. */
@@ -128,7 +152,7 @@ static int name_error_response_check_rr(int *flags, const knot_rrset_t *nsec,
 {
        assert(flags && nsec && name);
 
-       if (nsec_nonamematch(nsec, name) == 0) {
+       if (nsec_covers(nsec, name) == 0) {
                *flags |= FLG_NOEXIST_RRSET;
        }
 
@@ -144,7 +168,7 @@ static int name_error_response_check_rr(int *flags, const knot_rrset_t *nsec,
                *(--ptr) = '*';
                *(--ptr) = 1;
                /* True if this wildcard provably doesn't exist. */
-               if (nsec_nonamematch(nsec, ptr) == 0) {
+               if (nsec_covers(nsec, ptr) == 0) {
                        *flags |= FLG_NOEXIST_WILDCARD;
                        break;
                }
@@ -391,7 +415,7 @@ int kr_nsec_wildcard_answer_response_check(const knot_pkt_t *pkt, knot_section_t
                if (rrset->type != KNOT_RRTYPE_NSEC) {
                        continue;
                }
-               if (nsec_nonamematch(rrset, sname) == 0) {
+               if (nsec_covers(rrset, sname) == 0) {
                        return kr_ok();
                }
        }
index c86a9a980d4fba59367859475918985fd8c6b2ac..7d78a8cfb918c752e5e7ce9e069b461f4bfcc64c 100644 (file)
 
 /**
  * Check whether bitmap contains given type.
- * @param bm      Bitmap.
+ * @param bm      Bitmap from NSEC or NSEC3.
  * @param bm_size Bitmap size.
  * @param type    RR type to search for.
  * @return        True if bitmap contains type.
  */
 bool kr_nsec_bitmap_contains_type(const uint8_t *bm, uint16_t bm_size, uint16_t type);
 
+/**
+ * Check bitmap that child names are contained in the same zone.
+ * @note see RFC6840 4.1.
+ * @param bm      Bitmap from NSEC or NSEC3.
+ * @param bm_size Bitmap size.
+ * @return 0 if they are, >0 if not (abs(ENOENT)), <0 on error.
+ */
+int kr_nsec_children_in_zone_check(const uint8_t *bm, uint16_t bm_size);
+
 /**
  * Check an NSEC or NSEC3 bitmap for NODATA for a type.
  * @param bm      Bitmap.
index 88f6f119bc4f8a789fbdd5e9ae41ee0d76fb6226..da5539cfcd260c044fd76672a3eb04594b286779 100644 (file)
@@ -391,6 +391,18 @@ static int closest_encloser_proof(const knot_pkt_t *pkt,
                if (rrset->type != KNOT_RRTYPE_NSEC3) {
                        continue;
                }
+               /* Also skip the NSEC3-to-match an ancestor of sname if it's
+                * a parent-side delegation, as that would mean the owner
+                * does not really exist (authoritatively in this zone,
+                * even in case of opt-out).
+                */
+               uint8_t *bm = NULL;
+               uint16_t bm_size;
+               knot_nsec3_bitmap(&rrset->rrs, 0, &bm, &bm_size);
+               if (kr_nsec_children_in_zone_check(bm, bm_size) != 0) {
+                       continue; /* no fatal errors from bad RRs */
+               }
+               /* Match the NSEC3 to sname or one of its ancestors. */
                unsigned skipped = 0;
                flags = 0;
                int ret = closest_encloser_match(&flags, rrset, sname, &skipped);
@@ -401,6 +413,7 @@ static int closest_encloser_proof(const knot_pkt_t *pkt,
                        continue;
                }
                matching = rrset;
+               /* Construct the next closer name and try to cover it. */
                --skipped;
                next_closer = sname;
                for (unsigned j = 0; j < skipped; ++j) {