diff --git a/lib/dnssec/nsec.c b/lib/dnssec/nsec.c index 52bfb94300580cb8cae3ca31a964d3e640e4dc12..0b9193f076c9814f86f7c70e6f72c678b9171e8f 100644 --- a/lib/dnssec/nsec.c +++ b/lib/dnssec/nsec.c @@ -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(); } } diff --git a/lib/dnssec/nsec.h b/lib/dnssec/nsec.h index c86a9a980d4fba59367859475918985fd8c6b2ac..7d78a8cfb918c752e5e7ce9e069b461f4bfcc64c 100644 --- a/lib/dnssec/nsec.h +++ b/lib/dnssec/nsec.h @@ -20,13 +20,22 @@ /** * 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. diff --git a/lib/dnssec/nsec3.c b/lib/dnssec/nsec3.c index 88f6f119bc4f8a789fbdd5e9ae41ee0d76fb6226..da5539cfcd260c044fd76672a3eb04594b286779 100644 --- a/lib/dnssec/nsec3.c +++ b/lib/dnssec/nsec3.c @@ -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) {