diff --git a/NEWS b/NEWS index c375ed0b0e01ded6b4b4eb45001bcb130a646aa9..b44f6c906e73f877291619e06c1e51f32ce04d2d 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,18 @@ +Knot Resolver 1.5.2 (2017-01-22) +================================ + +Security +-------- +- fix CVE-2018-1000002: insufficient DNSSEC validation, allowing + attackers to deny existence of some data by forging packets. + Some combinations pointed out in RFC 6840 sections 4.1 and 4.3 + were not taken into account. + +Bugfixes +-------- +- memcached: fix fallout from module rename in 1.5.1 + + Knot Resolver 1.5.1 (2017-12-12) ================================ diff --git a/config.mk b/config.mk index fcb62a37b8dbd2ad80e0116a0578efd5c458b1ae..0023b4b5cbc1a3904d57f78de08a0a84fed26fca 100644 --- a/config.mk +++ b/config.mk @@ -1,7 +1,7 @@ # Project MAJOR := 1 MINOR := 5 -PATCH := 1 +PATCH := 2 EXTRA := ABIVER := 4 BUILDMODE := dynamic diff --git a/lib/dnssec/nsec.c b/lib/dnssec/nsec.c index 3335b3e8f0e8493163f427b64de0422f52beacae..18aaae4e06a736d23cdbc12e78afd29b2eb45f47 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; } @@ -147,7 +171,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; } @@ -219,40 +243,74 @@ static int coverign_rrsig_labels(const knot_rrset_t *nsec, const knot_pktsection return ret; } -/** - * Perform check of RR type existence denial according to RFC4035 5.4, bullet 1. - * @param flags Flags to be set according to check outcome. - * @param nsec NSEC RR. - * @param type Type to be checked. - * @return 0 or error code. - */ -static int no_data_response_check_rrtype(int *flags, const knot_rrset_t *nsec, - uint16_t type) -{ - assert(flags && nsec); - uint8_t *bm = NULL; - uint16_t bm_size; - knot_nsec_bitmap(&nsec->rrs, &bm, &bm_size); +int kr_nsec_bitmap_nodata_check(const uint8_t *bm, uint16_t bm_size, uint16_t type) +{ + const int NO_PROOF = abs(ENOENT); if (!bm) { return kr_error(EINVAL); } + if (kr_nsec_bitmap_contains_type(bm, bm_size, type)) { + return NO_PROOF; + } - if (!kr_nsec_bitmap_contains_type(bm, bm_size, type)) { - /* The type is not listed in the NSEC bitmap. */ + if (type != KNOT_RRTYPE_CNAME + && kr_nsec_bitmap_contains_type(bm, bm_size, KNOT_RRTYPE_CNAME)) { + return NO_PROOF; + } + /* Special behavior around zone cuts. */ + switch (type) { + case KNOT_RRTYPE_DS: /* Security feature: in case of DS also check for SOA * non-existence to be more certain that we don't hold * a child-side NSEC by some mistake (e.g. when forwarding). * See RFC4035 5.2, next-to-last paragraph. */ - if (type != KNOT_RRTYPE_DS - || !kr_nsec_bitmap_contains_type(bm, bm_size, KNOT_RRTYPE_SOA)) { - *flags |= FLG_NOEXIST_RRTYPE; + if (kr_nsec_bitmap_contains_type(bm, bm_size, KNOT_RRTYPE_SOA)) { + return NO_PROOF; + } + break; + case KNOT_RRTYPE_CNAME: + /* Exception from the `default` rule. It's perhaps disputable, + * but existence of CNAME at zone apex is not allowed, so we + * consider a parent-side record to be enough to prove non-existence. */ + break; + default: + /* Parent-side delegation record isn't authoritative for non-DS; + * see RFC6840 4.1. */ + if (kr_nsec_bitmap_contains_type(bm, bm_size, KNOT_RRTYPE_NS) + && !kr_nsec_bitmap_contains_type(bm, bm_size, KNOT_RRTYPE_SOA)) { + return NO_PROOF; } + /* LATER(opt): perhaps short-circuit test if we repeat it here. */ } return kr_ok(); } +/** + * Attempt to prove NODATA given a matching NSEC. + * @param flags Flags to be set according to check outcome. + * @param nsec NSEC RR. + * @param type Type to be checked. + * @return 0 on success, abs(ENOENT) for no proof, or error code (<0). + * @note It's not a *full* proof, of course (wildcards, etc.) + * @TODO returning result via `flags` is just ugly. + */ +static int no_data_response_check_rrtype(int *flags, const knot_rrset_t *nsec, + uint16_t type) +{ + assert(flags && nsec); + + uint8_t *bm = NULL; + uint16_t bm_size = 0; + knot_nsec_bitmap(&nsec->rrs, &bm, &bm_size); + int ret = kr_nsec_bitmap_nodata_check(bm, bm_size, type); + if (ret == kr_ok()) { + *flags |= FLG_NOEXIST_RRTYPE; + } + return ret <= 0 ? ret : kr_ok(); +} + /** * Perform check for RR type wildcard existence denial according to RFC4035 5.4, bullet 1. * @param flags Flags to be set according to check outcome. @@ -360,7 +418,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(); } } @@ -476,11 +534,15 @@ int kr_nsec_ref_to_unsigned(const knot_pkt_t *pkt) int kr_nsec_matches_name_and_type(const knot_rrset_t *nsec, const knot_dname_t *name, uint16_t type) { - if (!nsec || !name) { - return (EINVAL); + /* It's not secure enough to just check a single bit for (some) other types, + * but we don't (currently) only use this API for NS. See RFC 6840 sec. 4. + */ + if (type != KNOT_RRTYPE_NS || !nsec || !name) { + assert(!EINVAL); + return kr_error(EINVAL); } if (!knot_dname_is_equal(nsec->owner, name)) { - return (ENOENT); + return kr_error(ENOENT); } uint8_t *bm = NULL; uint16_t bm_size = 0; @@ -488,8 +550,9 @@ int kr_nsec_matches_name_and_type(const knot_rrset_t *nsec, if (!bm) { return kr_error(EINVAL); } - if (!kr_nsec_bitmap_contains_type(bm, bm_size, type)) { - return (ENOENT); + if (kr_nsec_bitmap_contains_type(bm, bm_size, type)) { + return kr_ok(); + } else { + return kr_error(ENOENT); } - return kr_ok(); } diff --git a/lib/dnssec/nsec.h b/lib/dnssec/nsec.h index 0c4f23d0fc3ebcf0f7642e6067a11a0783f7c362..7d78a8cfb918c752e5e7ce9e069b461f4bfcc64c 100644 --- a/lib/dnssec/nsec.h +++ b/lib/dnssec/nsec.h @@ -20,13 +20,32 @@ /** * 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. + * @param bm_size Bitmap size. + * @param type RR type to check. + * @note This includes special checks for zone cuts, e.g. from RFC 6840 sec. 4. + * @return 0, abs(ENOENT) (no proof), kr_error(EINVAL) + */ +int kr_nsec_bitmap_nodata_check(const uint8_t *bm, uint16_t bm_size, uint16_t type); + /** * Name error response check (RFC4035 3.1.3.2; RFC4035 5.4, bullet 2). * @note No RRSIGs are validated. @@ -87,7 +106,7 @@ int kr_nsec_ref_to_unsigned(const knot_pkt_t *pkt); * Checks whether supplied NSEC RR matches the supplied name and type. * @param nsec NSEC RR. * @param name Name to be checked. - * @param type Type to be checked. + * @param type Type to be checked. Only use with NS! TODO (+copy&paste NSEC3) * @return 0 or error code. */ int kr_nsec_matches_name_and_type(const knot_rrset_t *nsec, diff --git a/lib/dnssec/nsec3.c b/lib/dnssec/nsec3.c index 1ebf3850ebe68baec0e170e4d23b8b2a8d3645d1..fd683cb6171ee898e246fb4c100ce61d4bbd5ddc 100644 --- a/lib/dnssec/nsec3.c +++ b/lib/dnssec/nsec3.c @@ -299,11 +299,11 @@ static bool has_optout(const knot_rrset_t *nsec3) * @param flags Flags to be set according to check outcome. * @param nsec3 NSEC3 RR. * @param name Name to be checked. - * @return 0 or error code. + * @return 0 if matching, >0 if not (abs(ENOENT)), or error code (<0). */ -static int matches_name(int *flags, const knot_rrset_t *nsec3, const knot_dname_t *name) +static int matches_name(const knot_rrset_t *nsec3, const knot_dname_t *name) { - assert(flags && nsec3 && name); + assert(nsec3 && name); dnssec_binary_t owner_hash = {0, }; uint8_t hash_data[MAX_HASH_BYTES] = {0, }; @@ -328,8 +328,9 @@ static int matches_name(int *flags, const knot_rrset_t *nsec3, const knot_dname_ if ((owner_hash.size == name_hash.size) && (memcmp(owner_hash.data, name_hash.data, owner_hash.size) == 0)) { - *flags |= FLG_NAME_MATCHED; ret = kr_ok(); + } else { + ret = abs(ENOENT); } fail: @@ -390,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); @@ -400,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) { @@ -512,81 +526,36 @@ int kr_nsec3_name_error_response_check(const knot_pkt_t *pkt, knot_section_t sec } /** - * Checks whether supplied NSEC3 RR matches the supplied name and type. - * @param flags Flags to be set according to check outcome. - * @param nsec3 NSEC3 RR. - * @param name Name to be checked. - * @param type Type to be checked. - * @return 0 or error code. - */ -static int matches_name_and_type(int *flags, const knot_rrset_t *nsec3, - const knot_dname_t *name, uint16_t type) -{ - assert(flags && nsec3 && name); - - int ret = matches_name(flags, nsec3, name); - if (ret != 0) { - return ret; - } - - if (!(*flags & FLG_NAME_MATCHED)) { - return kr_ok(); - } - - uint8_t *bm = NULL; - uint16_t bm_size; - knot_nsec3_bitmap(&nsec3->rrs, 0, &bm, &bm_size); - if (!bm) { - return kr_error(EINVAL); - } - - if (!kr_nsec_bitmap_contains_type(bm, bm_size, type)) { - *flags |= FLG_TYPE_BIT_MISSING; - if (type == KNOT_RRTYPE_CNAME) { - *flags |= FLG_CNAME_BIT_MISSING; - } - } - - if ((type != KNOT_RRTYPE_CNAME) && - !kr_nsec_bitmap_contains_type(bm, bm_size, KNOT_RRTYPE_CNAME)) { - *flags |= FLG_CNAME_BIT_MISSING; - } - - return kr_ok(); -} - -/** - * No data response check, no DS (RFC5155 7.2.3). + * Search the packet section for a matching NSEC3 with nodata-proving bitmap. * @param pkt Packet structure to be processed. * @param section_id Packet section to be processed. * @param sname Name to be checked. * @param stype Type to be checked. * @return 0 or error code. + * @note This does NOT check the opt-out case if type is DS; + * see RFC 5155 8.6. */ -static int no_data_response_no_ds(const knot_pkt_t *pkt, knot_section_t section_id, - const knot_dname_t *sname, uint16_t stype) +static int nodata_find(const knot_pkt_t *pkt, knot_section_t section_id, + const knot_dname_t *name, const uint16_t type) { const knot_pktsection_t *sec = knot_pkt_section(pkt, section_id); - if (!sec || !sname) { + if (!sec || !name) { return kr_error(EINVAL); } - int flags; for (unsigned i = 0; i < sec->count; ++i) { - const knot_rrset_t *rrset = knot_pkt_rr(sec, i); - if (rrset->type != KNOT_RRTYPE_NSEC3) { + const knot_rrset_t *nsec3 = knot_pkt_rr(sec, i); + /* Records causing any errors are simply skipped. */ + if (nsec3->type != KNOT_RRTYPE_NSEC3 + || matches_name(nsec3, name) != kr_ok()) { continue; + /* LATER(optim.): we repeatedly recompute the hash of `name` */ } - flags = 0; - int ret = matches_name_and_type(&flags, rrset, sname, stype); - if (ret != 0) { - return ret; - } - - if ((flags & FLG_NAME_MATCHED) && - (flags & FLG_TYPE_BIT_MISSING) && - (flags & FLG_CNAME_BIT_MISSING)) { + uint8_t *bm = NULL; + uint16_t bm_size; + knot_nsec3_bitmap(&nsec3->rrs, 0, &bm, &bm_size); + if (kr_nsec_bitmap_nodata_check(bm, bm_size, type) == kr_ok()) { return kr_ok(); } } @@ -610,38 +579,13 @@ static int matches_closest_encloser_wildcard(const knot_pkt_t *pkt, knot_section return kr_error(EINVAL); } - uint8_t wildcard[KNOT_DNAME_MAXLEN]; + uint8_t wildcard[KNOT_DNAME_MAXLEN]; /**< the source of synthesis */ int ret = prepend_asterisk(wildcard, sizeof(wildcard), encloser); if (ret < 0) { return ret; } assert(ret >= 3); - - int flags; - for (unsigned i = 0; i < sec->count; ++i) { - const knot_rrset_t *rrset = knot_pkt_rr(sec, i); - if (rrset->type != KNOT_RRTYPE_NSEC3) { - continue; - } - flags = 0; - - int ret = matches_name_and_type(&flags, rrset, wildcard, stype); - if (ret != 0) { - return ret; - } - - /* TODO -- The loop resembles no_data_response_no_ds() exept - * the following condition. - */ - if ((flags & FLG_NAME_MATCHED) && - (flags & FLG_TYPE_BIT_MISSING) && - (flags & FLG_CNAME_BIT_MISSING)) { - /* rfc5155 8.7 */ - return kr_ok(); - } - } - - return kr_error(ENOENT); + return nodata_find(pkt, section_id, wildcard, stype); } int kr_nsec3_wildcard_answer_response_check(const knot_pkt_t *pkt, knot_section_t section_id, @@ -682,7 +626,7 @@ int kr_nsec3_no_data(const knot_pkt_t *pkt, knot_section_t section_id, const knot_dname_t *sname, uint16_t stype) { /* DS record may be also matched by an existing NSEC3 RR. */ - int ret = no_data_response_no_ds(pkt, section_id, sname, stype); + int ret = nodata_find(pkt, section_id, sname, stype); if (ret == 0) { /* Satisfies RFC5155 8.5 and 8.6, both first paragraph. */ return ret; @@ -737,7 +681,6 @@ int kr_nsec3_ref_to_unsigned(const knot_pkt_t *pkt) if (!sec) { return kr_error(EINVAL); } - for (unsigned i = 0; i < sec->count; ++i) { const knot_rrset_t *ns = knot_pkt_rr(sec, i); if (ns->type == KNOT_RRTYPE_DS) { @@ -749,7 +692,6 @@ int kr_nsec3_ref_to_unsigned(const knot_pkt_t *pkt) int flags = 0; bool nsec3_found = false; - for (unsigned j = 0; j < sec->count; ++j) { const knot_rrset_t *nsec3 = knot_pkt_rr(sec, j); if (nsec3->type == KNOT_RRTYPE_DS) { @@ -759,17 +701,9 @@ int kr_nsec3_ref_to_unsigned(const knot_pkt_t *pkt) continue; } nsec3_found = true; - /* nsec3 found, check if owner name matches - * the delegation name - */ - int ret = matches_name(&flags, nsec3, ns->owner); - if (ret != 0) { - return kr_error(EINVAL); - } - if (!(flags & FLG_NAME_MATCHED)) { - /* nsec3 owner name does not match - * the delegation name - */ + /* nsec3 found, check if owner name matches the delegation name. + * Just skip in case of *any* errors. */ + if (matches_name(nsec3, ns->owner) != kr_ok()) { continue; } @@ -805,8 +739,8 @@ int kr_nsec3_ref_to_unsigned(const knot_pkt_t *pkt) */ const knot_dname_t *encloser_name = NULL; const knot_rrset_t *covering_next_nsec3 = NULL; - int ret = closest_encloser_proof(pkt, KNOT_AUTHORITY, ns->owner, &encloser_name, - NULL, &covering_next_nsec3); + int ret = closest_encloser_proof(pkt, KNOT_AUTHORITY, ns->owner, + &encloser_name, NULL, &covering_next_nsec3); if (ret != 0) { return kr_error(EINVAL); } @@ -823,11 +757,26 @@ int kr_nsec3_ref_to_unsigned(const knot_pkt_t *pkt) int kr_nsec3_matches_name_and_type(const knot_rrset_t *nsec3, const knot_dname_t *name, uint16_t type) { - int flags = 0; - int ret = matches_name_and_type(&flags, nsec3, name, type); - if (ret != kr_ok()) { - return ret; + /* It's not secure enough to just check a single bit for (some) other types, + * but we don't (currently) only use this API for NS. See RFC 6840 sec. 4. + */ + if (type != KNOT_RRTYPE_NS) { + assert(!EINVAL); + return kr_error(EINVAL); + } + int ret = matches_name(nsec3, name); + if (ret) { + return kr_error(ret); + } + uint8_t *bm = NULL; + uint16_t bm_size = 0; + knot_nsec3_bitmap(&nsec3->rrs, 0, &bm, &bm_size); + if (!bm) { + return kr_error(EINVAL); + } + if (kr_nsec_bitmap_contains_type(bm, bm_size, type)) { + return kr_ok(); + } else { + return kr_error(ENOENT); } - return ((flags & (FLG_NAME_MATCHED | FLG_TYPE_BIT_MISSING)) != FLG_NAME_MATCHED) ? - kr_error(ENOENT) : kr_ok(); } diff --git a/lib/dnssec/nsec3.h b/lib/dnssec/nsec3.h index b07af7e533e8f8bc5023293e3fdc6eead17a5c8c..33a396a468f0f253eb3a8c3f6ea8427459d400bc 100644 --- a/lib/dnssec/nsec3.h +++ b/lib/dnssec/nsec3.h @@ -72,10 +72,10 @@ int kr_nsec3_no_data(const knot_pkt_t *pkt, knot_section_t section_id, int kr_nsec3_ref_to_unsigned(const knot_pkt_t *pkt); /** - * Checks whether supplied NSEC3 RR matches the supplied name and type. + * Checks whether supplied NSEC3 RR matches the supplied name and NS type. * @param nsec3 NSEC3 RR. * @param name Name to be checked. - * @param type Type to be checked. + * @param type Type to be checked. Only use with NS! TODO * @return 0 or error code. */ int kr_nsec3_matches_name_and_type(const knot_rrset_t *nsec3,