summaryrefslogtreecommitdiffstats
path: root/debian/patches/CVE-2022-40188.patch
diff options
context:
space:
mode:
Diffstat (limited to 'debian/patches/CVE-2022-40188.patch')
-rw-r--r--debian/patches/CVE-2022-40188.patch160
1 files changed, 160 insertions, 0 deletions
diff --git a/debian/patches/CVE-2022-40188.patch b/debian/patches/CVE-2022-40188.patch
new file mode 100644
index 0000000..29edd74
--- /dev/null
+++ b/debian/patches/CVE-2022-40188.patch
@@ -0,0 +1,160 @@
+From: Chris Lamb <lamby@debian.org>
+Date: Thu, 6 Oct 2022 14:12:26 -0700
+Subject: [PATCH] lib/zonecut + iterator: limit large NS sets
+
+rom f6577a20e493c7fbdac124d7544bf1846b084185 Mon Sep 17 00:00:00 2001
+
+rom f6577a20e493c7fbdac124d7544bf1846b084185 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Vladim=C3=ADr=20=C4=8Cun=C3=A1t?= <vladimir.cunat@nic.cz>
+Date: Wed, 17 Aug 2022 16:34:06 +0200
+Subject: [PATCH] lib/zonecut + iterator: limit large NS sets
+
+It's a mitigation for CVE-2022-40188 and similar DoS attempts.
+It's using really trivial approaches, at least for now.
+---
+ lib/layer/iterate.c | 15 +++++++++++++++
+ lib/zonecut.c | 33 +++++++++++++++++++++++++++++++--
+ lib/zonecut.h | 2 ++
+ 3 files changed, 48 insertions(+), 2 deletions(-)
+
+diff --git a/lib/layer/iterate.c b/lib/layer/iterate.c
+index cf57cc5..f7dd630 100644
+--- a/lib/layer/iterate.c
++++ b/lib/layer/iterate.c
+@@ -193,6 +193,8 @@ static int update_nsaddr(const knot_rrset_t *rr, struct kr_query *query, int *gl
+ return KR_STATE_CONSUME;
+ }
+
++enum { GLUE_COUNT_THROTTLE = 26 };
++
+ /** @internal From \a pkt, fetch glue records for name \a ns, and update the cut etc.
+ *
+ * \param glue_cnt the number of accepted addresses (to be incremented)
+@@ -227,6 +229,10 @@ static void fetch_glue(knot_pkt_t *pkt, const knot_dname_t *ns, bool in_bailiwic
+ continue;
+ }
+ (void) update_nsaddr(rr, req->current_query, glue_cnt);
++ /* If we reach limit on total glue addresses,
++ * we only load the first one per NS name (the one just above). */
++ if (*glue_cnt > GLUE_COUNT_THROTTLE)
++ break;
+ }
+ }
+ }
+@@ -427,6 +433,7 @@ static int process_authority(knot_pkt_t *pkt, struct kr_request *req)
+ const knot_dname_t *current_zone_cut = qry->zone_cut.name;
+ bool ns_record_exists = false;
+ int glue_cnt = 0;
++ int ns_count = 0;
+ /* Update zone cut information. */
+ for (unsigned i = 0; i < ns->count; ++i) {
+ const knot_rrset_t *rr = knot_pkt_rr(ns, i);
+@@ -438,6 +445,11 @@ static int process_authority(knot_pkt_t *pkt, struct kr_request *req)
+ case KR_STATE_FAIL: return state; break;
+ default: /* continue */ break;
+ }
++
++ if (++ns_count >= 13) {
++ VERBOSE_MSG("<= authority: many glue NSs, skipping the rest\n");
++ break;
++ }
+ } else if (rr->type == KNOT_RRTYPE_SOA
+ && knot_dname_in_bailiwick(rr->owner, qry->zone_cut.name) > 0) {
+ /* SOA below cut in authority indicates different authority,
+@@ -465,6 +477,9 @@ static int process_authority(knot_pkt_t *pkt, struct kr_request *req)
+ VERBOSE_MSG("<= loaded %d glue addresses\n", glue_cnt);
+ }
+
++ if (glue_cnt > GLUE_COUNT_THROTTLE) {
++ VERBOSE_MSG("<= (some may have been omitted due to being too many)\n");
++ }
+
+ if ((qry->flags.DNSSEC_WANT) && (result == KR_STATE_CONSUME)) {
+ if (knot_wire_get_aa(pkt->wire) == 0 &&
+diff --git a/lib/zonecut.c b/lib/zonecut.c
+index 5e54d97..cc25062 100644
+--- a/lib/zonecut.c
++++ b/lib/zonecut.c
+@@ -299,6 +299,7 @@ int kr_zonecut_set_sbelt(struct kr_context *ctx, struct kr_zonecut *cut)
+
+ /** Fetch address for zone cut. Any rank is accepted (i.e. glue as well). */
+ static addrset_info_t fetch_addr(pack_t *addrs, const knot_dname_t *ns, uint16_t rrtype,
++ int *addr_budget,
+ knot_mm_t *mm_pool, const struct kr_query *qry)
+ // LATER(optim.): excessive data copying
+ {
+@@ -332,6 +333,12 @@ static addrset_info_t fetch_addr(pack_t *addrs, const knot_dname_t *ns, uint16_t
+ return AI_UNKNOWN;
+ }
+
++ *addr_budget -= cached_rr.rrs.count - 1;
++ if (*addr_budget < 0) {
++ cached_rr.rrs.count += *addr_budget;
++ *addr_budget = 0;
++ }
++
+ /* Reserve memory in *addrs. Implementation detail:
+ * pack_t cares for lengths, so we don't store those in the data. */
+ const size_t pack_extra_size = knot_rdataset_size(&cached_rr.rrs)
+@@ -406,6 +413,20 @@ static int fetch_ns(struct kr_context *ctx, struct kr_zonecut *cut,
+ return ret;
+ }
+
++ /* Consider at most 13 first NSs (like root). It's a trivial approach
++ * to limit our resources when choosing NSs. Otherwise DoS might be viable.
++ * We're not aware of any reasonable use case for having many NSs. */
++ if (ns_rds.count > 13) {
++ auto_free char *name_txt = kr_dname_text(name);
++ VERBOSE_MSG(qry, "NS %s too large, reducing from %d names\n",
++ name_txt, (int)ns_rds.count);
++ ns_rds.count = 13;
++ }
++ /* Also trivially limit the total address count:
++ * first A and first AAAA are for free per NS,
++ * but the rest get a shared small limit and get skipped if exhausted. */
++ int addr_budget = 8;
++
+ /* Insert name servers for this zone cut, addresses will be looked up
+ * on-demand (either from cache or iteratively) */
+ bool all_bad = true; /**< All NSs (seen so far) are in a bad state. */
+@@ -431,10 +452,10 @@ static int fetch_ns(struct kr_context *ctx, struct kr_zonecut *cut,
+ unsigned reputation = (cached) ? *cached : 0;
+ infos[0] = (reputation & KR_NS_NOIP4) || qry->flags.NO_IPV4
+ ? AI_REPUT
+- : fetch_addr(*pack, ns_name, KNOT_RRTYPE_A, cut->pool, qry);
++ : fetch_addr(*pack, ns_name, KNOT_RRTYPE_A, &addr_budget, cut->pool, qry);
+ infos[1] = (reputation & KR_NS_NOIP6) || qry->flags.NO_IPV6
+ ? AI_REPUT
+- : fetch_addr(*pack, ns_name, KNOT_RRTYPE_AAAA, cut->pool, qry);
++ : fetch_addr(*pack, ns_name, KNOT_RRTYPE_AAAA, &addr_budget, cut->pool, qry);
+
+ #if 0 /* rather unlikely to be useful unless changing some zcut code */
+ WITH_VERBOSE(qry) {
+@@ -480,6 +501,14 @@ static int fetch_ns(struct kr_context *ctx, struct kr_zonecut *cut,
+ VERBOSE_MSG(qry, "cut %s: all NSs bad, count = %d\n",
+ name_txt, (int)ns_rds.count);
+ } }
++
++ assert(addr_budget >= 0);
++ if (addr_budget <= 0) {
++ auto_free char *name_txt = kr_dname_text(name);
++ VERBOSE_MSG(qry, "NS %s have too many addresses together, reduced\n",
++ name_txt);
++ }
++
+ knot_rdataset_clear(&ns_rds, cut->pool);
+ return all_bad ? ELOOP : kr_ok();
+ }
+diff --git a/lib/zonecut.h b/lib/zonecut.h
+index 2808c66..70dd4df 100644
+--- a/lib/zonecut.h
++++ b/lib/zonecut.h
+@@ -151,6 +151,8 @@ int kr_zonecut_set_sbelt(struct kr_context *ctx, struct kr_zonecut *cut);
+ /**
+ * Populate zone cut address set from cache.
+ *
++ * The size is limited to avoid possibility of doing too much CPU work.
++ *
+ * @param ctx resolution context (to fetch data from LRU caches)
+ * @param cut zone cut to be populated
+ * @param name QNAME to start finding zone cut for