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;
}
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. */
{
assert(flags && nsec && name);
- if (nsec_nonamematch(nsec, name) == 0) {
+ if (nsec_covers(nsec, name) == 0) {
*flags |= FLG_NOEXIST_RRSET;
}
*(--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;
}
if (rrset->type != KNOT_RRTYPE_NSEC) {
continue;
}
- if (nsec_nonamematch(rrset, sname) == 0) {
+ if (nsec_covers(rrset, sname) == 0) {
return kr_ok();
}
}
/**
* 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.
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);
continue;
}
matching = rrset;
+ /* Construct the next closer name and try to cover it. */
--skipped;
next_closer = sname;
for (unsigned j = 0; j < skipped; ++j) {