summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--debian/changelog10
-rw-r--r--debian/patches/CVE-2019-10190.patch293
-rw-r--r--debian/patches/CVE-2019-10191.patch158
-rw-r--r--debian/patches/CVE-2019-19331.patch371
-rw-r--r--debian/patches/CVE-2020-12667-part1.patch90
-rw-r--r--debian/patches/CVE-2020-12667-part2.patch86
-rw-r--r--debian/patches/series5
7 files changed, 1013 insertions, 0 deletions
diff --git a/debian/changelog b/debian/changelog
index 4db2798..ed00751 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,13 @@
+knot-resolver (3.2.1-3+deb10u2) buster-security; urgency=high
+
+ * Non-maintainer upload by the LTS team.
+ * Fix CVE-2019-10190, CVE-2019-10191, CVE-2019-19331, CVE-2020-12667:
+ Several security vulnerabilities have been discovered in knot-resolver, a
+ caching, DNSSEC-validating DNS resolver which may allow remote attackers to
+ bypass DNSSEC validation or cause a denial-of-service.
+
+ -- Markus Koschany <apo@debian.org> Thu, 25 Apr 2024 14:42:02 +0200
+
knot-resolver (3.2.1-3+deb10u1) buster-security; urgency=high
* CVE-2022-4018: Prevent an issue where remote attackers could cause
diff --git a/debian/patches/CVE-2019-10190.patch b/debian/patches/CVE-2019-10190.patch
new file mode 100644
index 0000000..c13a7df
--- /dev/null
+++ b/debian/patches/CVE-2019-10190.patch
@@ -0,0 +1,293 @@
+From: Markus Koschany <apo@debian.org>
+Date: Mon, 11 Mar 2024 14:16:29 +0100
+Subject: CVE-2019-10190
+
+Bug-Debian: https://bugs.debian.org/932048
+Origin: https://gitlab.labs.nic.cz/knot/knot-resolver/merge_requests/827
+---
+ lib/layer/iterate.c | 2 +-
+ lib/resolve.c | 96 +++++++++++++++++++++++------------------
+ lib/resolve.h | 2 +-
+ modules/cookies/cookiemonster.c | 4 +-
+ modules/hints/hints.c | 2 +-
+ 5 files changed, 59 insertions(+), 47 deletions(-)
+
+diff --git a/lib/layer/iterate.c b/lib/layer/iterate.c
+index f7dd630..ee2607e 100644
+--- a/lib/layer/iterate.c
++++ b/lib/layer/iterate.c
+@@ -557,7 +557,7 @@ static int unroll_cname(knot_pkt_t *pkt, struct kr_request *req, bool referral,
+ } else {
+ int cnt_ = 0;
+ state = update_nsaddr(rr, query->parent, &cnt_);
+- if (state == KR_STATE_FAIL) {
++ if (state & KR_STATE_FAIL) {
+ return state;
+ }
+ }
+diff --git a/lib/resolve.c b/lib/resolve.c
+index 8718f25..b4b3657 100644
+--- a/lib/resolve.c
++++ b/lib/resolve.c
+@@ -458,7 +458,7 @@ static int answer_prepare(struct kr_request *req, knot_pkt_t *query)
+ static int write_extra_records(const rr_array_t *arr, uint16_t reorder, knot_pkt_t *answer)
+ {
+ for (size_t i = 0; i < arr->len; ++i) {
+- int err = knot_pkt_put_rotate(answer, 0, arr->at[i], reorder, 0);
++ int err = knot_pkt_put_rotate(answer, 0, arr->at[i], reorder, KNOT_PF_NOTRUNC);
+ if (err != KNOT_EOK) {
+ return err == KNOT_ESPACE ? kr_ok() : kr_error(err);
+ }
+@@ -548,42 +548,52 @@ static int answer_padding(struct kr_request *request)
+ return kr_ok();
+ }
+
+-static int answer_fail(struct kr_request *request)
++/* Make a clean SERVFAIL answer. */
++static void answer_fail(struct kr_request *request)
+ {
++ /* Note: OPT in SERVFAIL response is still useful for cookies/additional info. */
+ knot_pkt_t *answer = request->answer;
++ knot_rrset_t *opt_rr = answer->opt_rr; /* it gets NULLed below */
+ int ret = kr_pkt_clear_payload(answer);
+ knot_wire_clear_ad(answer->wire);
+ knot_wire_clear_aa(answer->wire);
+ knot_wire_set_rcode(answer->wire, KNOT_RCODE_SERVFAIL);
+- if (ret == 0 && answer->opt_rr) {
+- /* OPT in SERVFAIL response is still useful for cookies/additional info. */
++ if (ret == 0 && opt_rr) {
+ knot_pkt_begin(answer, KNOT_ADDITIONAL);
+ answer_padding(request); /* Ignore failed padding in SERVFAIL answer. */
+- ret = edns_put(answer, false);
++ answer->opt_rr = opt_rr;
++ edns_put(answer, false);
+ }
+- return ret;
+ }
+
+-static int answer_finalize(struct kr_request *request, int state)
++static void answer_finalize(struct kr_request *request)
+ {
+ struct kr_rplan *rplan = &request->rplan;
+ knot_pkt_t *answer = request->answer;
+
+- /* Always set SERVFAIL for bogus answers. */
+- if (state == KR_STATE_FAIL && rplan->pending.len > 0) {
+- struct kr_query *last = array_tail(rplan->pending);
+- if ((last->flags.DNSSEC_WANT) && (last->flags.DNSSEC_BOGUS)) {
+- return answer_fail(request);
+- }
+- }
+-
+- struct kr_query *last = rplan->resolved.len > 0 ? array_tail(rplan->resolved) : NULL;
++ struct kr_query *const last =
++ rplan->resolved.len > 0 ? array_tail(rplan->resolved) : NULL;
+ /* TODO ^^^^ this is slightly fragile */
+
++ if (!last) {
++ /* Suspicious: no kr_query got resolved (not even from cache),
++ * so let's (defensively) SERVFAIL the request.
++ * ATM many checks below depend on `last` anyway,
++ * so this helps to avoid surprises. */
++ answer_fail(request);
++ return;
++ }
++ /* TODO: clean this up in !660 or followup, and it isn't foolproof anyway. */
++ if (last->flags.DNSSEC_BOGUS
++ || (rplan->pending.len > 0 && array_tail(rplan->pending)->flags.DNSSEC_BOGUS)) {
++ answer_fail(request);
++ return;
++ }
++
+ /* AD flag. We can only change `secure` from true to false.
+ * Be conservative. Primary approach: check ranks of all RRs in wire.
+ * Only "negative answers" need special handling. */
+- bool secure = last != NULL && state == KR_STATE_DONE /*< suspicious otherwise */
++ bool secure = last != NULL && request->state == KR_STATE_DONE /*< suspicious otherwise */
+ && knot_pkt_qtype(answer) != KNOT_RRTYPE_RRSIG;
+ if (last && (last->flags.STUB)) {
+ secure = false; /* don't trust forwarding for now */
+@@ -604,7 +614,8 @@ static int answer_finalize(struct kr_request *request, int state)
+ if (write_extra_ranked_records(&request->answ_selected, reorder,
+ answer, &secure, &answ_all_cnames))
+ {
+- return answer_fail(request);
++ answer_fail(request);
++ return;
+ }
+ }
+
+@@ -614,25 +625,29 @@ static int answer_finalize(struct kr_request *request, int state)
+ }
+ if (write_extra_ranked_records(&request->auth_selected, reorder,
+ answer, &secure, NULL)) {
+- return answer_fail(request);
++ answer_fail(request);
++ return;
+ }
+ /* Write additional records. */
+ knot_pkt_begin(answer, KNOT_ADDITIONAL);
+ if (write_extra_records(&request->additional, reorder, answer)) {
+- return answer_fail(request);
++ answer_fail(request);
++ return;
+ }
+ /* Write EDNS information */
+ if (answer->opt_rr) {
+ if (request->qsource.flags.tls) {
+ if (answer_padding(request) != kr_ok()) {
+- return answer_fail(request);
++ answer_fail(request);
++ return;
+ }
+ }
+ knot_pkt_begin(answer, KNOT_ADDITIONAL);
+ int ret = knot_pkt_put(answer, KNOT_COMPR_HINT_NONE,
+ answer->opt_rr, KNOT_PF_FREE);
+ if (ret != KNOT_EOK) {
+- return answer_fail(request);
++ answer_fail(request);
++ return;
+ }
+ }
+
+@@ -667,8 +682,6 @@ static int answer_finalize(struct kr_request *request, int state)
+ if (!secure) {
+ knot_wire_clear_ad(answer->wire);
+ }
+-
+- return kr_ok();
+ }
+
+ static int query_finalize(struct kr_request *request, struct kr_query *qry, knot_pkt_t *pkt)
+@@ -852,7 +865,7 @@ static void update_nslist_score(struct kr_request *request, struct kr_query *qry
+ {
+ struct kr_context *ctx = request->ctx;
+ /* On successful answer, update preference list RTT and penalise timer */
+- if (request->state != KR_STATE_FAIL) {
++ if (!(request->state & KR_STATE_FAIL)) {
+ /* Update RTT information for preference list */
+ update_nslist_rtt(ctx, qry, src);
+ /* Do not complete NS address resolution on soft-fail. */
+@@ -928,7 +941,7 @@ int kr_resolve_consume(struct kr_request *request, const struct sockaddr *src, k
+ update_nslist_score(request, qry, src, packet);
+ }
+ /* Resolution failed, invalidate current NS. */
+- if (request->state == KR_STATE_FAIL) {
++ if (request->state & KR_STATE_FAIL) {
+ invalidate_ns(rplan, qry);
+ qry->flags.RESOLVED = false;
+ }
+@@ -1283,7 +1296,7 @@ static int zone_cut_check(struct kr_request *request, struct kr_query *qry, knot
+ int state = KR_STATE_FAIL;
+ do {
+ state = ns_fetch_cut(qry, requested_name, request, packet);
+- if (state == KR_STATE_DONE || state == KR_STATE_FAIL) {
++ if (state == KR_STATE_DONE || (state & KR_STATE_FAIL)) {
+ return state;
+ } else if (state == KR_STATE_CONSUME) {
+ requested_name = knot_wire_next_label(requested_name, NULL);
+@@ -1353,7 +1366,7 @@ int kr_resolve_produce(struct kr_request *request, struct sockaddr **dst, int *t
+ /* Resolve current query and produce dependent or finish */
+ request->state = KR_STATE_PRODUCE;
+ ITERATE_LAYERS(request, qry, produce, packet);
+- if (request->state != KR_STATE_FAIL && knot_wire_get_qr(packet->wire)) {
++ if (!(request->state & KR_STATE_FAIL) && knot_wire_get_qr(packet->wire)) {
+ /* Produced an answer from cache, consume it. */
+ qry->secret = 0;
+ request->state = KR_STATE_CONSUME;
+@@ -1541,7 +1554,7 @@ int kr_resolve_checkout(struct kr_request *request, const struct sockaddr *src,
+ * don't affect the resolution or rest of the processing. */
+ int state = request->state;
+ ITERATE_LAYERS(request, qry, checkout, packet, dst, type);
+- if (request->state == KR_STATE_FAIL) {
++ if (request->state & KR_STATE_FAIL) {
+ request->state = state; /* Restore */
+ return kr_error(ECANCELED);
+ }
+@@ -1589,25 +1602,24 @@ int kr_resolve_checkout(struct kr_request *request, const struct sockaddr *src,
+
+ int kr_resolve_finish(struct kr_request *request, int state)
+ {
++ request->state = state;
+ /* Finalize answer and construct wire-buffer. */
+ ITERATE_LAYERS(request, NULL, answer_finalize);
+- if (request->state == KR_STATE_FAIL) {
+- state = KR_STATE_FAIL;
+- } else if (answer_finalize(request, state) != 0) {
+- state = KR_STATE_FAIL;
+- }
++ answer_finalize(request);
+
+- /* Error during processing, internal failure */
+- if (state != KR_STATE_DONE) {
+- knot_pkt_t *answer = request->answer;
+- if (knot_wire_get_rcode(answer->wire) == KNOT_RCODE_NOERROR) {
+- knot_wire_clear_ad(answer->wire);
+- knot_wire_clear_aa(answer->wire);
+- knot_wire_set_rcode(answer->wire, KNOT_RCODE_SERVFAIL);
++ /* Defensive style, in case someone has forgotten.
++ * Beware: non-empty answers do make sense even with SERVFAIL case, etc. */
++ if (request->state != KR_STATE_DONE) {
++ uint8_t *wire = request->answer->wire;
++ switch (knot_wire_get_rcode(wire)) {
++ case KNOT_RCODE_NOERROR:
++ case KNOT_RCODE_NXDOMAIN:
++ knot_wire_clear_ad(wire);
++ knot_wire_clear_aa(wire);
++ knot_wire_set_rcode(wire, KNOT_RCODE_SERVFAIL);
+ }
+ }
+
+- request->state = state;
+ ITERATE_LAYERS(request, NULL, finish);
+
+ #ifndef NOVERBOSELOG
+diff --git a/lib/resolve.h b/lib/resolve.h
+index 60d80bb..18372a3 100644
+--- a/lib/resolve.h
++++ b/lib/resolve.h
+@@ -308,7 +308,7 @@ int kr_resolve_checkout(struct kr_request *request, const struct sockaddr *src,
+ * be destroyed, as it's owned by caller.
+ *
+ * @param request request state
+- * @param state either DONE or FAIL state
++ * @param state either DONE or FAIL state (to be assigned to request->state)
+ * @return DONE
+ */
+ KR_EXPORT
+diff --git a/modules/cookies/cookiemonster.c b/modules/cookies/cookiemonster.c
+index 7af7afa..6f00192 100644
+--- a/modules/cookies/cookiemonster.c
++++ b/modules/cookies/cookiemonster.c
+@@ -423,7 +423,7 @@ int check_request(kr_layer_t *ctx)
+ /* Request has no server cookie. */
+ return_state = invalid_sc_status(return_state, false,
+ ignore_badcookie, req, answer);
+- if (return_state == KR_STATE_FAIL) {
++ if (return_state & KR_STATE_FAIL) {
+ return return_state;
+ }
+ goto answer_add_cookies;
+@@ -449,7 +449,7 @@ int check_request(kr_layer_t *ctx)
+ /* Invalid server cookie. */
+ return_state = invalid_sc_status(return_state, true,
+ ignore_badcookie, req, answer);
+- if (return_state == KR_STATE_FAIL) {
++ if (return_state & KR_STATE_FAIL) {
+ return return_state;
+ }
+ goto answer_add_cookies;
+diff --git a/modules/hints/hints.c b/modules/hints/hints.c
+index 84e2e99..f55e85b 100644
+--- a/modules/hints/hints.c
++++ b/modules/hints/hints.c
+@@ -133,7 +133,7 @@ static int satisfy_forward(struct kr_zonecut *hints, knot_pkt_t *pkt, struct kr_
+ static int query(kr_layer_t *ctx, knot_pkt_t *pkt)
+ {
+ struct kr_query *qry = ctx->req->current_query;
+- if (!qry || ctx->state & (KR_STATE_FAIL)) {
++ if (!qry || (ctx->state & KR_STATE_FAIL)) {
+ return ctx->state;
+ }
+
diff --git a/debian/patches/CVE-2019-10191.patch b/debian/patches/CVE-2019-10191.patch
new file mode 100644
index 0000000..1d4a4b5
--- /dev/null
+++ b/debian/patches/CVE-2019-10191.patch
@@ -0,0 +1,158 @@
+From: Markus Koschany <apo@debian.org>
+Date: Mon, 11 Mar 2024 14:24:02 +0100
+Subject: CVE-2019-10191
+
+Bug-Debian: https://bugs.debian.org/932048
+Origin: https://gitlab.labs.nic.cz/knot/knot-resolver/merge_requests/839
+---
+ daemon/lua/kres-gen.lua | 1 +
+ daemon/worker.c | 5 ++++-
+ lib/cache/api.c | 4 +++-
+ lib/cache/impl.h | 3 ++-
+ lib/layer.h | 7 ++++++-
+ lib/layer/iterate.c | 11 ++++++++++-
+ lib/resolve.c | 2 ++
+ lib/rplan.h | 2 ++
+ 8 files changed, 30 insertions(+), 5 deletions(-)
+
+diff --git a/daemon/lua/kres-gen.lua b/daemon/lua/kres-gen.lua
+index eeb8ff7..9e9f586 100644
+--- a/daemon/lua/kres-gen.lua
++++ b/daemon/lua/kres-gen.lua
+@@ -120,6 +120,7 @@ struct kr_qflags {
+ _Bool DNS64_MARK : 1;
+ _Bool CACHE_TRIED : 1;
+ _Bool NO_NS_FOUND : 1;
++ _Bool PKT_IS_SANE : 1;
+ };
+ typedef struct {
+ knot_rrset_t **at;
+diff --git a/daemon/worker.c b/daemon/worker.c
+index 117cc91..1235979 100644
+--- a/daemon/worker.c
++++ b/daemon/worker.c
+@@ -1611,8 +1611,11 @@ int worker_submit(struct session *session, knot_pkt_t *query)
+ return kr_error(ENOMEM);
+ }
+ } else if (query) { /* response from upstream */
+- task = session_tasklist_del_msgid(session, knot_wire_get_id(query->wire));
++ const uint16_t id = knot_wire_get_id(query->wire);
++ task = session_tasklist_del_msgid(session, id);
+ if (task == NULL) {
++ VERBOSE_MSG(NULL, "=> ignoring packet with mismatching ID %d\n",
++ (int)id);
+ return kr_error(ENOENT);
+ }
+ assert(!session_flags(session)->closing);
+diff --git a/lib/cache/api.c b/lib/cache/api.c
+index c0591d6..4c7f3d2 100644
+--- a/lib/cache/api.c
++++ b/lib/cache/api.c
+@@ -408,7 +408,9 @@ int cache_stash(kr_layer_t *ctx, knot_pkt_t *pkt)
+ /* LATER(optim.): typically we also have corresponding NS record in the list,
+ * so we might save a cache operation. */
+
+- stash_pkt(pkt, qry, req, has_optout);
++ if (qry->flags.PKT_IS_SANE && check_dname_for_lf(knot_pkt_qname(pkt), qry)) {
++ stash_pkt(pkt, qry, req, has_optout);
++ }
+
+ finally:
+ if (unauth_cnt) {
+diff --git a/lib/cache/impl.h b/lib/cache/impl.h
+index a08f355..ffb5e67 100644
+--- a/lib/cache/impl.h
++++ b/lib/cache/impl.h
+@@ -253,7 +253,8 @@ void entry_list_memcpy(struct entry_apex *ea, entry_list_t list);
+ /* Packet caching; implementation in ./entry_pkt.c */
+
+ /** Stash the packet into cache (if suitable, etc.)
+- * \param has_optout whether the packet contains an opt-out NSEC3 */
++ * \param has_optout whether the packet contains an opt-out NSEC3
++ * It assumes check_dname_for_lf(). */
+ void stash_pkt(const knot_pkt_t *pkt, const struct kr_query *qry,
+ const struct kr_request *req, bool has_optout);
+
+diff --git a/lib/layer.h b/lib/layer.h
+index 0909cb7..011d279 100644
+--- a/lib/layer.h
++++ b/lib/layer.h
+@@ -48,7 +48,12 @@
+ enum kr_layer_state {
+ KR_STATE_CONSUME = 1 << 0, /*!< Consume data. */
+ KR_STATE_PRODUCE = 1 << 1, /*!< Produce data. */
+- KR_STATE_DONE = 1 << 2, /*!< Finished successfully. */
++
++ /*! Finished successfully or a special case: in CONSUME phase this can
++ * be used (by iterator) to do a transition to PRODUCE phase again,
++ * in which case the packet wasn't accepted for some reason. */
++ KR_STATE_DONE = 1 << 2,
++
+ KR_STATE_FAIL = 1 << 3, /*!< Error. */
+ KR_STATE_YIELD = 1 << 4, /*!< Paused, waiting for a sub-query. */
+ };
+diff --git a/lib/layer/iterate.c b/lib/layer/iterate.c
+index ee2607e..feaadf0 100644
+--- a/lib/layer/iterate.c
++++ b/lib/layer/iterate.c
+@@ -81,10 +81,14 @@ static bool is_paired_to_query(const knot_pkt_t *answer, struct kr_query *query)
+ uint16_t qtype = query->stype;
+ const knot_dname_t *qname = minimized_qname(query, &qtype);
+
++ /* ID should already match, thanks to session_tasklist_del_msgid()
++ * in worker_submit(), but it won't hurt to check again. */
+ return query->id == knot_wire_get_id(answer->wire) &&
+- knot_wire_get_qdcount(answer->wire) > 0 &&
++ knot_wire_get_qdcount(answer->wire) == 1 &&
+ query->sclass == knot_pkt_qclass(answer) &&
+ qtype == knot_pkt_qtype(answer) &&
++ /* qry->secret had been xor-applied to answer already,
++ * so this also checks for correctness of case randomization */
+ knot_dname_is_equal(qname, knot_pkt_qname(answer));
+ }
+
+@@ -1029,6 +1033,7 @@ static int resolve(kr_layer_t *ctx, knot_pkt_t *pkt)
+ if (!query) {
+ return ctx->state;
+ }
++ query->flags.PKT_IS_SANE = false;
+
+ WITH_VERBOSE(query) {
+ if (query->flags.TRACE) {
+@@ -1072,6 +1077,10 @@ static int resolve(kr_layer_t *ctx, knot_pkt_t *pkt)
+ return KR_STATE_CONSUME;
+ }
+
++ /* If exiting above here, there's no sense to put it into packet cache.
++ * The most important part is to check for spoofing: is_paired_to_query() */
++ query->flags.PKT_IS_SANE = true;
++
+ #ifndef NOVERBOSELOG
+ const knot_lookup_t *rcode = knot_lookup_by_id(knot_rcode_names, knot_wire_get_rcode(pkt->wire));
+ #endif
+diff --git a/lib/resolve.c b/lib/resolve.c
+index b4b3657..b771cdc 100644
+--- a/lib/resolve.c
++++ b/lib/resolve.c
+@@ -150,6 +150,8 @@ static void randomized_qname_case(knot_dname_t * restrict qname, uint32_t secret
+ assert(qname);
+ const int len = knot_dname_size(qname) - 2; /* Skip first, last label. */
+ for (int i = 0; i < len; ++i) {
++ /* Note: this relies on the fact that correct label lengths
++ * can't pass the isletter() test (by "luck"). */
+ if (isletter(*++qname)) {
+ *qname ^= ((secret >> (i & 31)) & 1) * 0x20;
+ }
+diff --git a/lib/rplan.h b/lib/rplan.h
+index 21b0b0b..7e82b03 100644
+--- a/lib/rplan.h
++++ b/lib/rplan.h
+@@ -64,6 +64,8 @@ struct kr_qflags {
+ bool DNS64_MARK : 1; /**< Internal mark for dns64 module. */
+ bool CACHE_TRIED : 1; /**< Internal to cache module. */
+ bool NO_NS_FOUND : 1; /**< No valid NS found during last PRODUCE stage. */
++ bool PKT_IS_SANE : 1; /**< Set by iterator in consume phase to indicate whether
++ * some basic aspects of the packet are OK, e.g. QNAME. */
+ };
+
+ /** Combine flags together. This means set union for simple flags. */
diff --git a/debian/patches/CVE-2019-19331.patch b/debian/patches/CVE-2019-19331.patch
new file mode 100644
index 0000000..67420ea
--- /dev/null
+++ b/debian/patches/CVE-2019-19331.patch
@@ -0,0 +1,371 @@
+From: Markus Koschany <apo@debian.org>
+Date: Mon, 11 Mar 2024 14:37:42 +0200
+Subject: CVE-2019-19331
+
+Bug-Debian: https://bugs.debian.org/946181
+Origin: https://gitlab.nic.cz/knot/knot-resolver/-/commit/782682db6d4b17d9d1559a1d13ae718a07eb260e
+---
+ daemon/lua/kres-gen.lua | 2 +
+ daemon/lua/kres-gen.sh | 1 +
+ lib/cache/api.c | 1 +
+ lib/dnssec.c | 1 +
+ lib/layer/iterate.c | 17 +++++-
+ lib/resolve.c | 1 +
+ lib/utils.c | 143 +++++++++++++++++++++++++++++++++++++++++++++---
+ lib/utils.h | 10 +++-
+ modules/dns64/dns64.lua | 1 +
+ 9 files changed, 167 insertions(+), 10 deletions(-)
+
+diff --git a/daemon/lua/kres-gen.lua b/daemon/lua/kres-gen.lua
+index 9e9f586..a92288b 100644
+--- a/daemon/lua/kres-gen.lua
++++ b/daemon/lua/kres-gen.lua
+@@ -135,6 +135,7 @@ struct ranked_rr_array_entry {
+ _Bool yielded : 1;
+ _Bool to_wire : 1;
+ _Bool expiring : 1;
++ _Bool in_progress : 1;
+ knot_rrset_t *rr;
+ };
+ typedef struct ranked_rr_array_entry ranked_rr_array_entry_t;
+@@ -310,6 +311,7 @@ int kr_bitcmp(const char *, const char *, int);
+ int kr_family_len(int);
+ struct sockaddr *kr_straddr_socket(const char *, int);
+ int kr_ranked_rrarray_add(ranked_rr_array_t *, const knot_rrset_t *, uint8_t, _Bool, uint32_t, knot_mm_t *);
++int kr_ranked_rrarray_finalize(ranked_rr_array_t *, uint32_t, knot_mm_t *);
+ void kr_qflags_set(struct kr_qflags *, struct kr_qflags);
+ void kr_qflags_clear(struct kr_qflags *, struct kr_qflags);
+ int kr_zonecut_add(struct kr_zonecut *, const knot_dname_t *, const void *, int);
+diff --git a/daemon/lua/kres-gen.sh b/daemon/lua/kres-gen.sh
+index 538fe23..6c4aa64 100755
+--- a/daemon/lua/kres-gen.sh
++++ b/daemon/lua/kres-gen.sh
+@@ -163,6 +163,7 @@ EOF
+ kr_family_len
+ kr_straddr_socket
+ kr_ranked_rrarray_add
++ kr_ranked_rrarray_finalize
+ kr_qflags_set
+ kr_qflags_clear
+ kr_zonecut_add
+diff --git a/lib/cache/api.c b/lib/cache/api.c
+index 4c7f3d2..90aaf0b 100644
+--- a/lib/cache/api.c
++++ b/lib/cache/api.c
+@@ -605,6 +605,7 @@ static int stash_rrarray_entry(ranked_rr_array_t *arr, int arr_i,
+ /* TODO: ATM we assume that some properties are the same
+ * for all RRSIGs in the set (esp. label count). */
+ ranked_rr_array_entry_t *e = arr->at[j];
++ assert(!e->in_progress);
+ bool ok = e->qry_uid == qry->uid && !e->cached
+ && e->rr->type == KNOT_RRTYPE_RRSIG
+ && knot_rrsig_type_covered(e->rr->rrs.rdata) == rr->type
+diff --git a/lib/dnssec.c b/lib/dnssec.c
+index 4f8ad8a..9973656 100644
+--- a/lib/dnssec.c
++++ b/lib/dnssec.c
+@@ -447,6 +447,7 @@ int kr_dnssec_matches_name_and_type(const ranked_rr_array_t *rrs, uint32_t qry_u
+ int ret = kr_error(ENOENT);
+ for (size_t i = 0; i < rrs->len; ++i) {
+ const ranked_rr_array_entry_t *entry = rrs->at[i];
++ assert(!entry->in_progress);
+ const knot_rrset_t *nsec = entry->rr;
+ if (entry->qry_uid != qry_uid || entry->yielded) {
+ continue;
+diff --git a/lib/layer/iterate.c b/lib/layer/iterate.c
+index feaadf0..0423a3d 100644
+--- a/lib/layer/iterate.c
++++ b/lib/layer/iterate.c
+@@ -1119,13 +1119,15 @@ static int resolve(kr_layer_t *ctx, knot_pkt_t *pkt)
+ return resolve_error(pkt, req);
+ }
+
++ int state;
+ /* Forwarding/stub mode is special. */
+ if (query->flags.STUB) {
+- return process_stub(pkt, req);
++ state = process_stub(pkt, req);
++ goto rrarray_finalize;
+ }
+
+ /* Resolve authority to see if it's referral or authoritative. */
+- int state = process_authority(pkt, req);
++ state = process_authority(pkt, req);
+ switch(state) {
+ case KR_STATE_CONSUME: /* Not referral, process answer. */
+ VERBOSE_MSG("<= rcode: %s\n", rcode ? rcode->name : "??");
+@@ -1139,6 +1141,17 @@ static int resolve(kr_layer_t *ctx, knot_pkt_t *pkt)
+ break;
+ }
+
++rrarray_finalize:
++ /* Finish construction of libknot-format RRsets. */
++ (void)0;
++ ranked_rr_array_t *selected[] = kr_request_selected(req);
++ for (knot_section_t i = KNOT_ANSWER; i <= KNOT_ADDITIONAL; ++i) {
++ int ret = kr_ranked_rrarray_finalize(selected[i], query->uid, &req->pool);
++ if (unlikely(ret)) {
++ return KR_STATE_FAIL;
++ }
++ }
++
+ return state;
+ }
+
+diff --git a/lib/resolve.c b/lib/resolve.c
+index b771cdc..65b167f 100644
+--- a/lib/resolve.c
++++ b/lib/resolve.c
+@@ -484,6 +484,7 @@ static int write_extra_ranked_records(const ranked_rr_array_t *arr, uint16_t reo
+
+ for (size_t i = 0; i < arr->len; ++i) {
+ ranked_rr_array_entry_t * entry = arr->at[i];
++ assert(!entry->in_progress);
+ if (!entry->to_wire) {
+ continue;
+ }
+diff --git a/lib/utils.c b/lib/utils.c
+index fe9ab03..2b093f6 100644
+--- a/lib/utils.c
++++ b/lib/utils.c
+@@ -677,6 +677,11 @@ static int to_wire_ensure_unique(ranked_rr_array_t *array, size_t index)
+ return kr_ok();
+ }
+
++/* Implementation overview of _add() and _finalize():
++ * - for rdata we just maintain a list of pointers (in knot_rrset_t::additional)
++ * - we only construct the final rdataset at the end (and thus more efficiently)
++ */
++typedef array_t(knot_rdata_t *) rdata_array_t;
+ int kr_ranked_rrarray_add(ranked_rr_array_t *array, const knot_rrset_t *rr,
+ uint8_t rank, bool to_wire, uint32_t qry_uid, knot_mm_t *pool)
+ {
+@@ -691,47 +696,93 @@ int kr_ranked_rrarray_add(ranked_rr_array_t *array, const knot_rrset_t *rr,
+ }
+ if (stashed->qry_uid != qry_uid) {
+ break;
++ /* We do not guarantee merging RRs "across" any point that switched
++ * to processing a different upstream packet (i.e. qry_uid).
++ * In particular, iterator never returns KR_STATE_YIELD. */
+ }
+ if (!rrsets_match(stashed->rr, rr)) {
+ continue;
+ }
+ /* Found the entry to merge with. Check consistency and merge. */
+- bool ok = stashed->rank == rank && !stashed->cached;
++ bool ok = stashed->rank == rank && !stashed->cached && stashed->in_progress;
+ if (!ok) {
+ assert(false);
+ return kr_error(EEXIST);
+ }
++ /* assert(rr->rrs.count == 1); */
++ /* ^^ shouldn't be a problem for this function, but it's probably a bug */
++
+ /* It may happen that an RRset is first considered useful
+ * (to_wire = false, e.g. due to being part of glue),
+ * and later we may find we also want it in the answer. */
+ stashed->to_wire = stashed->to_wire || to_wire;
+
+- return knot_rdataset_merge(&stashed->rr->rrs, &rr->rrs, pool);
++ /* We just add the reference into this in_progress RRset. */
++ rdata_array_t *ra = stashed->rr->additional;
++ if (ra == NULL) {
++ /* RRset not in array format yet -> convert it. */
++ ra = stashed->rr->additional = mm_alloc(pool, sizeof(*ra));
++ if (!ra) {
++ return kr_error(ENOMEM);
++ }
++ array_init(*ra);
++ int ret = array_reserve_mm(*ra, stashed->rr->rrs.count + rr->rrs.count,
++ kr_memreserve, pool);
++ if (ret) {
++ return kr_error(ret);
++ }
++ knot_rdata_t *r_it = stashed->rr->rrs.rdata;
++ for (int ri = 0; ri < stashed->rr->rrs.count;
++ ++ri, r_it = knot_rdataset_next(r_it)) {
++ if (array_push(*ra, r_it) < 0) {
++ abort();
++ }
++ }
++ } else {
++ int ret = array_reserve_mm(*ra, ra->len + rr->rrs.count,
++ kr_memreserve, pool);
++ if (ret) {
++ return kr_error(ret);
++ }
++ }
++ /* Append to the array. */
++ knot_rdata_t *r_it = rr->rrs.rdata;
++ for (int ri = 0; ri < rr->rrs.count;
++ ++ri, r_it = knot_rdataset_next(r_it)) {
++ if (array_push(*ra, r_it) < 0) {
++ abort();
++ }
++ }
++ return kr_ok();
+ }
+
+ /* No stashed rrset found, add */
+ int ret = array_reserve_mm(*array, array->len + 1, kr_memreserve, pool);
+- if (ret != 0) {
+- return kr_error(ENOMEM);
++ if (ret) {
++ return kr_error(ret);
+ }
+
+ ranked_rr_array_entry_t *entry = mm_alloc(pool, sizeof(ranked_rr_array_entry_t));
+ if (!entry) {
+ return kr_error(ENOMEM);
+ }
+- knot_rrset_t *copy = knot_rrset_copy(rr, pool);
+- if (!copy) {
++
++ knot_rrset_t *rr_new = knot_rrset_new(rr->owner, rr->type, rr->rclass, rr->ttl, pool);
++ if (!rr_new) {
+ mm_free(pool, entry);
+ return kr_error(ENOMEM);
+ }
++ rr_new->rrs = rr->rrs;
++ assert(rr_new->additional == NULL);
+
+ entry->qry_uid = qry_uid;
+- entry->rr = copy;
++ entry->rr = rr_new;
+ entry->rank = rank;
+ entry->revalidation_cnt = 0;
+ entry->cached = false;
+ entry->yielded = false;
+ entry->to_wire = to_wire;
++ entry->in_progress = true;
+ if (array_push(*array, entry) < 0) {
+ /* Silence coverity. It shouldn't be possible to happen,
+ * due to the array_reserve_mm call above. */
+@@ -742,6 +793,84 @@ int kr_ranked_rrarray_add(ranked_rr_array_t *array, const knot_rrset_t *rr,
+ return to_wire_ensure_unique(array, array->len - 1);
+ }
+
++/** Comparator for qsort() on an array of knot_data_t pointers. */
++static int rdata_p_cmp(const void *rp1, const void *rp2)
++{
++ /* Just correct types of the parameters and pass them dereferenced. */
++ const knot_rdata_t
++ *const *r1 = rp1,
++ *const *r2 = rp2;
++ return knot_rdata_cmp(*r1, *r2);
++}
++int kr_ranked_rrarray_finalize(ranked_rr_array_t *array, uint32_t qry_uid, knot_mm_t *pool)
++{
++ for (ssize_t array_i = array->len - 1; array_i >= 0; --array_i) {
++ ranked_rr_array_entry_t *stashed = array->at[array_i];
++ if (stashed->qry_uid != qry_uid) {
++ continue; /* We apparently can't always short-cut the cycle. */
++ }
++ if (!stashed->in_progress) {
++ continue;
++ }
++ rdata_array_t *ra = stashed->rr->additional;
++ if (!ra) {
++ /* No array, so we just need to copy the rdataset. */
++ knot_rdataset_t *rds = &stashed->rr->rrs;
++ knot_rdataset_t tmp = *rds;
++ int ret = knot_rdataset_copy(rds, &tmp, pool);
++ if (ret) {
++ return kr_error(ret);
++ }
++ } else {
++ /* Multiple RRs; first: sort the array. */
++ stashed->rr->additional = NULL;
++ qsort(ra->at, ra->len, sizeof(ra->at[0]), rdata_p_cmp);
++ /* Prune duplicates: NULL all except the last instance. */
++ int dup_count = 0;
++ for (int i = 0; i + 1 < ra->len; ++i) {
++ if (knot_rdata_cmp(ra->at[i], ra->at[i + 1]) == 0) {
++ ra->at[i] = NULL;
++ ++dup_count;
++ QRVERBOSE(NULL, "iter", "deleted duplicate RR\n");
++ }
++ }
++ /* Prepare rdataset, except rdata contents. */
++ int size_sum = 0;
++ for (int i = 0; i < ra->len; ++i) {
++ if (ra->at[i]) {
++ size_sum += knot_rdata_size(ra->at[i]->len);
++ }
++ }
++ knot_rdataset_t *rds = &stashed->rr->rrs;
++ rds->count = ra->len - dup_count;
++ #if KNOT_VERSION_HEX >= 0x020900
++ rds->size = size_sum;
++ #endif
++ if (size_sum) {
++ rds->rdata = mm_alloc(pool, size_sum);
++ if (!rds->rdata) {
++ return kr_error(ENOMEM);
++ }
++ } else {
++ rds->rdata = NULL;
++ }
++ /* Everything is ready; now just copy all the rdata. */
++ uint8_t *raw_it = (uint8_t *)rds->rdata;
++ for (int i = 0; i < ra->len; ++i) {
++ if (ra->at[i] && size_sum/*linters*/) {
++ const int size = knot_rdata_size(ra->at[i]->len);
++ memcpy(raw_it, ra->at[i], size);
++ raw_it += size;
++ }
++ }
++ assert(raw_it == (uint8_t *)rds->rdata + size_sum);
++ }
++ stashed->in_progress = false;
++ }
++ return kr_ok();
++}
++
++
+ int kr_ranked_rrarray_set_wire(ranked_rr_array_t *array, bool to_wire,
+ uint32_t qry_uid, bool check_dups,
+ bool (*extraCheck)(const ranked_rr_array_entry_t *))
+diff --git a/lib/utils.h b/lib/utils.h
+index 21eabac..3040e66 100644
+--- a/lib/utils.h
++++ b/lib/utils.h
+@@ -165,6 +165,7 @@ struct ranked_rr_array_entry {
+ bool yielded : 1;
+ bool to_wire : 1; /**< whether to be put into the answer */
+ bool expiring : 1; /**< low remaining TTL; see is_expiring; only used in cache ATM */
++ bool in_progress : 1; /**< build of RRset in progress, i.e. different format of RR data */
+ knot_rrset_t *rr;
+ };
+ typedef struct ranked_rr_array_entry ranked_rr_array_entry_t;
+@@ -351,10 +352,17 @@ KR_EXPORT
+ int kr_rrkey(char *key, uint16_t class, const knot_dname_t *owner,
+ uint16_t type, uint16_t additional);
+
+-/** @internal Add RRSet copy to ranked RR array. */
++/** Add RRSet copy to a ranked RR array.
++ *
++ * To convert to standard RRs inside, you need to call _finalize() afterwards,
++ * and the memory of rr->rrs.rdata has to remain until then.
++ */
+ KR_EXPORT
+ int kr_ranked_rrarray_add(ranked_rr_array_t *array, const knot_rrset_t *rr,
+ uint8_t rank, bool to_wire, uint32_t qry_uid, knot_mm_t *pool);
++/** Finalize in_progress sets - all with matching qry_uid. */
++KR_EXPORT
++int kr_ranked_rrarray_finalize(ranked_rr_array_t *array, uint32_t qry_uid, knot_mm_t *pool);
+
+ /** @internal Mark the RRSets from particular query as
+ * "have (not) to be recorded in the final answer".
+diff --git a/modules/dns64/dns64.lua b/modules/dns64/dns64.lua
+index 8e3eaa0..8700661 100644
+--- a/modules/dns64/dns64.lua
++++ b/modules/dns64/dns64.lua
+@@ -98,6 +98,7 @@ function M.layer.consume(state, req, pkt)
+ req.pool)
+ end
+ end
++ ffi.C.kr_ranked_rrarray_finalize(req.answ_selected, qry.uid, req.pool);
+ end
+
+ return M
diff --git a/debian/patches/CVE-2020-12667-part1.patch b/debian/patches/CVE-2020-12667-part1.patch
new file mode 100644
index 0000000..e8505f1
--- /dev/null
+++ b/debian/patches/CVE-2020-12667-part1.patch
@@ -0,0 +1,90 @@
+From: Markus Koschany <apo@debian.org>
+Date: Mon, 11 Mar 2024 16:40:42 +0200
+Subject: CVE-2020-12667 part1
+
+Bug-Debian: https://bugs.debian.org/961076
+Origin: https://gitlab.labs.nic.cz/knot/knot-resolver/-/commit/54f05e4d7b2e47c0bdd30b84272fc503cc65304b
+---
+ daemon/lua/kres-gen.lua | 1 +
+ lib/defines.h | 1 +
+ lib/resolve.c | 15 +++++++++++----
+ lib/resolve.h | 1 +
+ 4 files changed, 14 insertions(+), 4 deletions(-)
+
+diff --git a/daemon/lua/kres-gen.lua b/daemon/lua/kres-gen.lua
+index a92288b..ad2f40e 100644
+--- a/daemon/lua/kres-gen.lua
++++ b/daemon/lua/kres-gen.lua
+@@ -200,6 +200,7 @@ struct kr_request {
+ knot_mm_t pool;
+ unsigned int uid;
+ void *daemon_context;
++ unsigned int count_no_nsaddr;
+ };
+ enum kr_rank {KR_RANK_INITIAL, KR_RANK_OMIT, KR_RANK_TRY, KR_RANK_INDET = 4, KR_RANK_BOGUS, KR_RANK_MISMATCH, KR_RANK_MISSING, KR_RANK_INSECURE, KR_RANK_AUTH = 16, KR_RANK_SECURE = 32};
+ struct kr_cache {
+diff --git a/lib/defines.h b/lib/defines.h
+index 84da059..aa3a349 100644
+--- a/lib/defines.h
++++ b/lib/defines.h
+@@ -64,6 +64,7 @@ static inline int KR_COLD kr_error(int x) {
+ #define KR_CNAME_CHAIN_LIMIT 40 /* Built-in maximum CNAME chain length */
+ #define KR_TIMEOUT_LIMIT 4 /* Maximum number of retries after timeout. */
+ #define KR_QUERY_NSRETRY_LIMIT 4 /* Maximum number of retries per query. */
++#define KR_COUNT_NO_NSADDR_LIMIT 5
+
+ /*
+ * Defines.
+diff --git a/lib/resolve.c b/lib/resolve.c
+index 65b167f..7e1f334 100644
+--- a/lib/resolve.c
++++ b/lib/resolve.c
+@@ -306,10 +306,10 @@ static int ns_fetch_cut(struct kr_query *qry, const knot_dname_t *requested_name
+ return KR_STATE_PRODUCE;
+ }
+
+-static int ns_resolve_addr(struct kr_query *qry, struct kr_request *param)
++static int ns_resolve_addr(struct kr_query *qry, struct kr_request *req)
+ {
+- struct kr_rplan *rplan = &param->rplan;
+- struct kr_context *ctx = param->ctx;
++ struct kr_rplan *rplan = &req->rplan;
++ struct kr_context *ctx = req->ctx;
+
+
+ /* Start NS queries from root, to avoid certain cases
+@@ -340,7 +340,9 @@ static int ns_resolve_addr(struct kr_query *qry, struct kr_request *param)
+ return kr_error(EAGAIN);
+ }
+ /* No IPv4 nor IPv6, flag server as unusable. */
+- VERBOSE_MSG(qry, "=> unresolvable NS address, bailing out\n");
++ ++req->count_no_nsaddr;
++ VERBOSE_MSG(qry, "=> unresolvable NS address, bailing out (counter: %u)\n",
++ req->count_no_nsaddr);
+ qry->ns.reputation |= KR_NS_NOIP4 | KR_NS_NOIP6;
+ kr_nsrep_update_rep(&qry->ns, qry->ns.reputation, ctx->cache_rep);
+ invalidate_ns(rplan, qry);
+@@ -1408,6 +1410,11 @@ int kr_resolve_produce(struct kr_request *request, struct sockaddr **dst, int *t
+
+ ns_election:
+
++ if (unlikely(request->count_no_nsaddr >= KR_COUNT_NO_NSADDR_LIMIT)) {
++ VERBOSE_MSG(qry, "=> too many unresolvable NSs, bail out "
++ "(mitigation for NXNSAttack CVE-2020-12667)\n");
++ return KR_STATE_FAIL;
++ }
+ /* If the query has already selected a NS and is waiting for IPv4/IPv6 record,
+ * elect best address only, otherwise elect a completely new NS.
+ */
+diff --git a/lib/resolve.h b/lib/resolve.h
+index 18372a3..5900a8c 100644
+--- a/lib/resolve.h
++++ b/lib/resolve.h
+@@ -233,6 +233,7 @@ struct kr_request {
+ knot_mm_t pool;
+ unsigned int uid; /** for logging purposes only */
+ void *daemon_context; /** pointer to worker from daemon. Can be used in modules. */
++ unsigned int count_no_nsaddr;
+ };
+
+ /** Initializer for an array of *_selected. */
diff --git a/debian/patches/CVE-2020-12667-part2.patch b/debian/patches/CVE-2020-12667-part2.patch
new file mode 100644
index 0000000..7ca9173
--- /dev/null
+++ b/debian/patches/CVE-2020-12667-part2.patch
@@ -0,0 +1,86 @@
+From: Markus Koschany <apo@debian.org>
+Date: Mon, 11 Mar 2024 16:41:21 +0200
+Subject: CVE-2020-12667 part2
+
+Bug-Debian: https://bugs.debian.org/961076
+Origin: https://gitlab.labs.nic.cz/knot/knot-resolver/-/commit/ba7b89db780fe3884b4e90090318e25ee5afb118
+---
+ daemon/lua/kres-gen.lua | 1 +
+ lib/defines.h | 1 +
+ lib/layer/iterate.c | 3 ++-
+ lib/resolve.c | 11 +++++++++++
+ lib/resolve.h | 1 +
+ 5 files changed, 16 insertions(+), 1 deletion(-)
+
+diff --git a/daemon/lua/kres-gen.lua b/daemon/lua/kres-gen.lua
+index ad2f40e..5867f0f 100644
+--- a/daemon/lua/kres-gen.lua
++++ b/daemon/lua/kres-gen.lua
+@@ -201,6 +201,7 @@ struct kr_request {
+ unsigned int uid;
+ void *daemon_context;
+ unsigned int count_no_nsaddr;
++ unsigned int count_fail_row;
+ };
+ enum kr_rank {KR_RANK_INITIAL, KR_RANK_OMIT, KR_RANK_TRY, KR_RANK_INDET = 4, KR_RANK_BOGUS, KR_RANK_MISMATCH, KR_RANK_MISSING, KR_RANK_INSECURE, KR_RANK_AUTH = 16, KR_RANK_SECURE = 32};
+ struct kr_cache {
+diff --git a/lib/defines.h b/lib/defines.h
+index aa3a349..2eee6cf 100644
+--- a/lib/defines.h
++++ b/lib/defines.h
+@@ -65,6 +65,7 @@ static inline int KR_COLD kr_error(int x) {
+ #define KR_TIMEOUT_LIMIT 4 /* Maximum number of retries after timeout. */
+ #define KR_QUERY_NSRETRY_LIMIT 4 /* Maximum number of retries per query. */
+ #define KR_COUNT_NO_NSADDR_LIMIT 5
++#define KR_CONSUME_FAIL_ROW_LIMIT 3 /* Maximum number of KR_STATE_FAIL in a row. */
+
+ /*
+ * Defines.
+diff --git a/lib/layer/iterate.c b/lib/layer/iterate.c
+index 0423a3d..40087a1 100644
+--- a/lib/layer/iterate.c
++++ b/lib/layer/iterate.c
+@@ -875,7 +875,8 @@ static int process_stub(knot_pkt_t *pkt, struct kr_request *req)
+ }
+
+
+-/** Error handling, RFC1034 5.3.3, 4d. */
++/** Error handling, RFC1034 5.3.3, 4d.
++ * NOTE: returing this does not prevent further queries (by itself). */
+ static int resolve_error(knot_pkt_t *pkt, struct kr_request *req)
+ {
+ return KR_STATE_FAIL;
+diff --git a/lib/resolve.c b/lib/resolve.c
+index 7e1f334..fbe1aab 100644
+--- a/lib/resolve.c
++++ b/lib/resolve.c
+@@ -951,6 +951,17 @@ int kr_resolve_consume(struct kr_request *request, const struct sockaddr *src, k
+ qry->flags.RESOLVED = false;
+ }
+
++ /* For multiple errors in a row; invalidate_ns() is not enough. */
++ if (!qry->flags.CACHED) {
++ if (request->state & KR_STATE_FAIL) {
++ if (++request->count_fail_row > KR_CONSUME_FAIL_ROW_LIMIT) {
++ return KR_STATE_FAIL;
++ }
++ } else {
++ request->count_fail_row = 0;
++ }
++ }
++
+ /* Pop query if resolved. */
+ if (request->state == KR_STATE_YIELD) {
+ return KR_STATE_PRODUCE; /* Requery */
+diff --git a/lib/resolve.h b/lib/resolve.h
+index 5900a8c..4d8b385 100644
+--- a/lib/resolve.h
++++ b/lib/resolve.h
+@@ -234,6 +234,7 @@ struct kr_request {
+ unsigned int uid; /** for logging purposes only */
+ void *daemon_context; /** pointer to worker from daemon. Can be used in modules. */
+ unsigned int count_no_nsaddr;
++ unsigned int count_fail_row;
+ };
+
+ /** Initializer for an array of *_selected. */
diff --git a/debian/patches/series b/debian/patches/series
index 825dbcf..fe61811 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -2,3 +2,8 @@
0002-avoid-invocations-of-git-during-make-installcheck.patch
0003-Avoid-clobbering-CXX-flags-when-compiling-lua-aho-co.patch
CVE-2022-40188.patch
+CVE-2019-10190.patch
+CVE-2019-10191.patch
+CVE-2019-19331.patch
+CVE-2020-12667-part1.patch
+CVE-2020-12667-part2.patch