From: =?utf-8?b?VmxhZGltw61yIMSMdW7DoXQ=?= Date: Mon, 1 Jan 2024 16:21:10 +0100 Subject: mitigate KeyTrap DoS = CVE-2023-50387 --- daemon/engine.c | 1 + daemon/lua/kres-gen-30.lua | 3 +++ daemon/lua/kres-gen-31.lua | 3 +++ daemon/lua/kres-gen-32.lua | 3 +++ lib/defines.h | 2 ++ lib/dnssec.c | 28 ++++++++++++++++++++++++++++ lib/dnssec.h | 1 + lib/layer/validate.c | 7 +++++++ lib/resolve.h | 3 +++ lib/rplan.h | 6 ++++++ 10 files changed, 57 insertions(+) diff --git a/daemon/engine.c b/daemon/engine.c index 26c225f..a3f439f 100644 --- a/daemon/engine.c +++ b/daemon/engine.c @@ -492,6 +492,7 @@ static int init_resolver(struct engine *engine) /* Open resolution context */ ctx->trust_anchors = trie_create(NULL); ctx->negative_anchors = trie_create(NULL); + ctx->vld_limit_crypto = KR_VLD_LIMIT_CRYPTO_DEFAULT; ctx->pool = engine->pool; ctx->modules = &engine->modules; ctx->cache_rtt_tout_retry_interval = KR_NS_TIMEOUT_RETRY_INTERVAL; diff --git a/daemon/lua/kres-gen-30.lua b/daemon/lua/kres-gen-30.lua index 4353c5c..01ae6ac 100644 --- a/daemon/lua/kres-gen-30.lua +++ b/daemon/lua/kres-gen-30.lua @@ -336,6 +336,8 @@ struct kr_query { struct kr_qflags forward_flags; uint32_t secret; uint32_t uid; + int32_t vld_limit_crypto_remains; + uint32_t vld_limit_uid; uint64_t creation_time_mono; uint64_t timestamp_mono; struct timeval timestamp; @@ -353,6 +355,7 @@ struct kr_context { knot_rrset_t *upstream_opt_rr; trie_t *trust_anchors; trie_t *negative_anchors; + int32_t vld_limit_crypto; struct kr_zonecut root_hints; struct kr_cache cache; unsigned int cache_rtt_tout_retry_interval; diff --git a/daemon/lua/kres-gen-31.lua b/daemon/lua/kres-gen-31.lua index a68dd65..c3845ec 100644 --- a/daemon/lua/kres-gen-31.lua +++ b/daemon/lua/kres-gen-31.lua @@ -336,6 +336,8 @@ struct kr_query { struct kr_qflags forward_flags; uint32_t secret; uint32_t uid; + int32_t vld_limit_crypto_remains; + uint32_t vld_limit_uid; uint64_t creation_time_mono; uint64_t timestamp_mono; struct timeval timestamp; @@ -353,6 +355,7 @@ struct kr_context { knot_rrset_t *upstream_opt_rr; trie_t *trust_anchors; trie_t *negative_anchors; + int32_t vld_limit_crypto; struct kr_zonecut root_hints; struct kr_cache cache; unsigned int cache_rtt_tout_retry_interval; diff --git a/daemon/lua/kres-gen-32.lua b/daemon/lua/kres-gen-32.lua index 222891e..2c407d4 100644 --- a/daemon/lua/kres-gen-32.lua +++ b/daemon/lua/kres-gen-32.lua @@ -337,6 +337,8 @@ struct kr_query { struct kr_qflags forward_flags; uint32_t secret; uint32_t uid; + int32_t vld_limit_crypto_remains; + uint32_t vld_limit_uid; uint64_t creation_time_mono; uint64_t timestamp_mono; struct timeval timestamp; @@ -354,6 +356,7 @@ struct kr_context { knot_rrset_t *upstream_opt_rr; trie_t *trust_anchors; trie_t *negative_anchors; + int32_t vld_limit_crypto; struct kr_zonecut root_hints; struct kr_cache cache; unsigned int cache_rtt_tout_retry_interval; diff --git a/lib/defines.h b/lib/defines.h index 6b6dac5..e832892 100644 --- a/lib/defines.h +++ b/lib/defines.h @@ -57,6 +57,8 @@ static inline int KR_COLD kr_error(int x) { #define KR_COUNT_NO_NSADDR_LIMIT 5 #define KR_CONSUME_FAIL_ROW_LIMIT 3 /* Maximum number of KR_STATE_FAIL in a row. */ +#define KR_VLD_LIMIT_CRYPTO_DEFAULT 32 /**< default for struct kr_query::vld_limit_crypto */ + /* * Defines. */ diff --git a/lib/dnssec.c b/lib/dnssec.c index c536357..1e6eb58 100644 --- a/lib/dnssec.c +++ b/lib/dnssec.c @@ -240,6 +240,29 @@ fail: return NULL; } +/// Return if we want to afford yet another crypto-validation (and account it). +static bool check_crypto_limit(const kr_rrset_validation_ctx_t *vctx) +{ + if (vctx->limit_crypto_remains == NULL) + return true; // no limiting + if (*vctx->limit_crypto_remains > 0) { + --*vctx->limit_crypto_remains; + return true; + } + // We got over limit. There are optional actions to do. + if (vctx->log_qry && kr_log_is_debug_qry(VALIDATOR, vctx->log_qry)) { + auto_free char *name_str = kr_dname_text(vctx->zone_name); + kr_log_q(vctx->log_qry, VALIDATOR, + "expensive crypto limited, mitigating CVE-2023-50387, current zone: %s\n", + name_str); + } + if (vctx->log_qry && vctx->log_qry->request) { + kr_request_set_extended_error(vctx->log_qry->request, KNOT_EDNS_EDE_BOGUS, + "EAIE: expensive crypto limited, mitigating CVE-2023-50387"); + } + return false; +} + static int kr_svldr_rrset_with_key(knot_rrset_t *rrs, const knot_rdataset_t *rrsigs, kr_rrset_validation_ctx_t *vctx, const kr_svldr_key_t *key) { @@ -258,6 +281,8 @@ static int kr_svldr_rrset_with_key(knot_rrset_t *rrs, const knot_rdataset_t *rrs } else if (retv != 0) { continue; } + if (!check_crypto_limit(vctx)) + return vctx->result = kr_error(E2BIG); // We only expect non-expanded wildcard records in input; // that also means we don't need to perform non-existence proofs. const int trim_labels = (val_flgs & FLG_WILDCARD_EXPANSION) ? 1 : 0; @@ -367,6 +392,9 @@ static int kr_rrset_validate_with_key(kr_rrset_validation_ctx_t *vctx, break; } } + if (!check_crypto_limit(vctx)) { + goto finish; + } if (kr_check_signature(rdata_j, key, covered, trim_labels) != 0) { vctx->rrs_counters.crypto_invalid++; continue; diff --git a/lib/dnssec.h b/lib/dnssec.h index 0fbd47c..ca737cf 100644 --- a/lib/dnssec.h +++ b/lib/dnssec.h @@ -44,6 +44,7 @@ struct kr_rrset_validation_ctx { uint32_t flags; /*!< Output - Flags. */ uint32_t err_cnt; /*!< Output - Number of validation failures. */ uint32_t cname_norrsig_cnt; /*!< Output - Number of CNAMEs missing RRSIGs. */ + int32_t *limit_crypto_remains; /*!< Optional pointer to struct kr_query::vld_limit_crypto_remains */ /** Validation result: kr_error() code. * diff --git a/lib/layer/validate.c b/lib/layer/validate.c index 6c0d7ad..4882da8 100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@ -276,6 +276,7 @@ static int validate_records(struct kr_request *req, knot_pkt_t *answer, knot_mm_ .err_cnt = 0, .cname_norrsig_cnt = 0, .result = 0, + .limit_crypto_remains = &qry->vld_limit_crypto_remains, .log_qry = qry, }; @@ -384,6 +385,7 @@ static int validate_keyset(struct kr_request *req, knot_pkt_t *answer, bool has_ .has_nsec3 = has_nsec3, .flags = 0, .result = 0, + .limit_crypto_remains = &qry->vld_limit_crypto_remains, .log_qry = qry, }; int ret = kr_dnskeys_trusted(&vctx, sig_rds, qry->zone_cut.trust_anchor); @@ -1030,6 +1032,11 @@ static int validate(kr_layer_t *ctx, knot_pkt_t *pkt) struct kr_request *req = ctx->req; struct kr_query *qry = req->current_query; + if (qry->vld_limit_uid != qry->uid) { + qry->vld_limit_uid = qry->uid; + qry->vld_limit_crypto_remains = req->ctx->vld_limit_crypto; + } + /* Ignore faulty or unprocessed responses. */ if (ctx->state & (KR_STATE_FAIL|KR_STATE_CONSUME)) { return ctx->state; diff --git a/lib/resolve.h b/lib/resolve.h index 97ba07b..a2d5ec9 100644 --- a/lib/resolve.h +++ b/lib/resolve.h @@ -162,6 +162,9 @@ struct kr_context trie_t *trust_anchors; trie_t *negative_anchors; + /** Validator's limit on the number of cryptographic steps for a single upstream packet. */ + int32_t vld_limit_crypto; + struct kr_zonecut root_hints; struct kr_cache cache; unsigned cache_rtt_tout_retry_interval; diff --git a/lib/rplan.h b/lib/rplan.h index 891781f..68174af 100644 --- a/lib/rplan.h +++ b/lib/rplan.h @@ -87,6 +87,12 @@ struct kr_query { struct kr_qflags forward_flags; uint32_t secret; uint32_t uid; /**< Query iteration number, unique within the kr_rplan. */ + + /** Remaining limit; see kr_query::vld_limit_crypto docs */ + int32_t vld_limit_crypto_remains; + /** ::uid value to which this remaining limit applies. */ + uint32_t vld_limit_uid; + uint64_t creation_time_mono; /* The time of query's creation (milliseconds). * Or time of creation of an oldest * ancestor if it is a subquery. */