summaryrefslogtreecommitdiff
path: root/backport-CVE-2023-50387.patch
diff options
context:
space:
mode:
Diffstat (limited to 'backport-CVE-2023-50387.patch')
-rw-r--r--backport-CVE-2023-50387.patch189
1 files changed, 189 insertions, 0 deletions
diff --git a/backport-CVE-2023-50387.patch b/backport-CVE-2023-50387.patch
new file mode 100644
index 0000000..58a71cf
--- /dev/null
+++ b/backport-CVE-2023-50387.patch
@@ -0,0 +1,189 @@
+From 1ebdb19ff194120109b08bbf888bdcc502f83211 Mon Sep 17 00:00:00 2001
+From: Ronan Pigott <ronan@rjp.ie>
+Date: Sat, 24 Feb 2024 18:21:24 -0700
+Subject: [PATCH] resolved: limit the number of signature validations in a
+ transaction
+
+It has been demonstrated that tolerating an unbounded number of dnssec
+signature validations is a bad idea. It is easy for a maliciously
+crafted DNS reply to contain as many keytag collisions as desired,
+causing us to iterate every dnskey and signature combination in vain.
+
+The solution is to impose a maximum number of validations we will
+tolerate. While collisions are not hard to craft, I still expect they
+are unlikely in the wild so it should be safe to pick fairly small
+values.
+
+Here two limits are imposed: one on the maximum number of invalid
+signatures encountered per rrset, and another on the total number of
+validations performed per transaction.
+
+(cherry picked from commit 67d0ce8843d612a2245d0966197d4f528b911b66)
+
+Conflict:NA
+Reference:https://github.com/systemd/systemd-stable/commit/1ebdb19ff194120109b08bbf888bdcc502f83211
+
+---
+ src/resolve/resolved-dns-dnssec.c | 16 ++++++++++++++--
+ src/resolve/resolved-dns-dnssec.h | 9 ++++++++-
+ src/resolve/resolved-dns-transaction.c | 19 ++++++++++++++++---
+ 3 files changed, 38 insertions(+), 6 deletions(-)
+
+diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c
+index 2580c2333c..aa87820dca 100644
+--- a/src/resolve/resolved-dns-dnssec.c
++++ b/src/resolve/resolved-dns-dnssec.c
+@@ -1169,6 +1169,7 @@ int dnssec_verify_rrset_search(
+ DnsResourceRecord **ret_rrsig) {
+
+ bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false;
++ unsigned nvalidations = 0;
+ DnsResourceRecord *rrsig;
+ int r;
+
+@@ -1214,6 +1215,14 @@ int dnssec_verify_rrset_search(
+ if (realtime == USEC_INFINITY)
+ realtime = now(CLOCK_REALTIME);
+
++ /* Have we seen an unreasonable number of invalid signaures? */
++ if (nvalidations > DNSSEC_INVALID_MAX) {
++ if (ret_rrsig)
++ *ret_rrsig = NULL;
++ *result = DNSSEC_TOO_MANY_VALIDATIONS;
++ return (int) nvalidations;
++ }
++
+ /* Yay, we found a matching RRSIG with a matching
+ * DNSKEY, awesome. Now let's verify all entries of
+ * the RRSet against the RRSIG and DNSKEY
+@@ -1223,6 +1232,8 @@ int dnssec_verify_rrset_search(
+ if (r < 0)
+ return r;
+
++ nvalidations++;
++
+ switch (one_result) {
+
+ case DNSSEC_VALIDATED:
+@@ -1233,7 +1244,7 @@ int dnssec_verify_rrset_search(
+ *ret_rrsig = rrsig;
+
+ *result = one_result;
+- return 0;
++ return (int) nvalidations;
+
+ case DNSSEC_INVALID:
+ /* If the signature is invalid, let's try another
+@@ -1280,7 +1291,7 @@ int dnssec_verify_rrset_search(
+ if (ret_rrsig)
+ *ret_rrsig = NULL;
+
+- return 0;
++ return (int) nvalidations;
+ }
+
+ int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) {
+@@ -2564,6 +2575,7 @@ static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
+ [DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
+ [DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
+ [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server",
++ [DNSSEC_TOO_MANY_VALIDATIONS] = "too-many-validations",
+ };
+ DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);
+
+diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h
+index 954bb3ef9d..29b90130a3 100644
+--- a/src/resolve/resolved-dns-dnssec.h
++++ b/src/resolve/resolved-dns-dnssec.h
+@@ -9,12 +9,13 @@ typedef enum DnssecVerdict DnssecVerdict;
+ #include "resolved-dns-rr.h"
+
+ enum DnssecResult {
+- /* These five are returned by dnssec_verify_rrset() */
++ /* These six are returned by dnssec_verify_rrset() */
+ DNSSEC_VALIDATED,
+ DNSSEC_VALIDATED_WILDCARD, /* Validated via a wildcard RRSIG, further NSEC/NSEC3 checks necessary */
+ DNSSEC_INVALID,
+ DNSSEC_SIGNATURE_EXPIRED,
+ DNSSEC_UNSUPPORTED_ALGORITHM,
++ DNSSEC_TOO_MANY_VALIDATIONS,
+
+ /* These two are added by dnssec_verify_rrset_search() */
+ DNSSEC_NO_SIGNATURE,
+@@ -45,6 +46,12 @@ enum DnssecVerdict {
+ /* The longest digest we'll ever generate, of all digest algorithms we support */
+ #define DNSSEC_HASH_SIZE_MAX (MAX(20, 32))
+
++/* The most invalid signatures we will tolerate for a single rrset */
++#define DNSSEC_INVALID_MAX 5
++
++/* The total number of signature validations we will tolerate for a single transaction */
++#define DNSSEC_VALIDATION_MAX 64
++
+ int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok);
+ int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig);
+
+diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c
+index 6c931d71dc..8ff5653dff 100644
+--- a/src/resolve/resolved-dns-transaction.c
++++ b/src/resolve/resolved-dns-transaction.c
+@@ -3163,11 +3163,14 @@ static int dnssec_validate_records(
+ DnsTransaction *t,
+ Phase phase,
+ bool *have_nsec,
++ unsigned *nvalidations,
+ DnsAnswer **validated) {
+
+ DnsResourceRecord *rr;
+ int r;
+
++ assert(nvalidations);
++
+ /* Returns negative on error, 0 if validation failed, 1 to restart validation, 2 when finished. */
+
+ DNS_ANSWER_FOREACH(rr, t->answer) {
+@@ -3209,6 +3212,7 @@ static int dnssec_validate_records(
+ &rrsig);
+ if (r < 0)
+ return r;
++ *nvalidations += r;
+
+ log_debug("Looking at %s: %s", strna(dns_resource_record_to_string(rr)), dnssec_result_to_string(result));
+
+@@ -3406,7 +3410,8 @@ static int dnssec_validate_records(
+ DNSSEC_SIGNATURE_EXPIRED,
+ DNSSEC_NO_SIGNATURE))
+ manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, rr->key);
+- else /* DNSSEC_MISSING_KEY or DNSSEC_UNSUPPORTED_ALGORITHM */
++ else /* DNSSEC_MISSING_KEY, DNSSEC_UNSUPPORTED_ALGORITHM,
++ or DNSSEC_TOO_MANY_VALIDATIONS */
+ manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, rr->key);
+
+ /* This is a primary response to our question, and it failed validation.
+@@ -3499,13 +3504,21 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
+ return r;
+
+ phase = DNSSEC_PHASE_DNSKEY;
+- for (;;) {
++ for (unsigned nvalidations = 0;;) {
+ bool have_nsec = false;
+
+- r = dnssec_validate_records(t, phase, &have_nsec, &validated);
++ r = dnssec_validate_records(t, phase, &have_nsec, &nvalidations, &validated);
+ if (r <= 0)
+ return r;
+
++ if (nvalidations > DNSSEC_VALIDATION_MAX) {
++ /* This reply requires an onerous number of signature validations to verify. Let's
++ * not waste our time trying, as this shouldn't happen for well-behaved domains
++ * anyway. */
++ t->answer_dnssec_result = DNSSEC_TOO_MANY_VALIDATIONS;
++ return 0;
++ }
++
+ /* Try again as long as we managed to achieve something */
+ if (r == 1)
+ continue;
+--
+2.33.0
+