From 9f1ac11479107643347ac0973e4cbb8a61f569f2 Mon Sep 17 00:00:00 2001 From: eabdullin Date: Thu, 14 Mar 2024 18:54:22 +0000 Subject: [PATCH] import UBI dnsmasq-2.79-31.el8_9.2 --- ...dnsmasq-2.80-synth-domain-RHEL-15216.patch | 28 + ...q-2.90-CVE-2023-50387-CVE-2023-50868.patch | 2318 +++++++++++++++++ SPECS/dnsmasq.spec | 16 +- 3 files changed, 2361 insertions(+), 1 deletion(-) create mode 100644 SOURCES/dnsmasq-2.80-synth-domain-RHEL-15216.patch create mode 100644 SOURCES/dnsmasq-2.90-CVE-2023-50387-CVE-2023-50868.patch diff --git a/SOURCES/dnsmasq-2.80-synth-domain-RHEL-15216.patch b/SOURCES/dnsmasq-2.80-synth-domain-RHEL-15216.patch new file mode 100644 index 0000000..0a01ea0 --- /dev/null +++ b/SOURCES/dnsmasq-2.80-synth-domain-RHEL-15216.patch @@ -0,0 +1,28 @@ +From 0a970b2a19c9fe5166e846d8a0c8b4f4fa5f1b4f Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Mon, 30 Jul 2018 14:55:39 +0100 +Subject: [PATCH] Fix crash parsing a --synth-domain with no prefix. Problem + introduced in 2.79/6b2b564ac34cb3c862f168e6b1457f9f0b9ca69c + +--- + src/option.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/option.c b/src/option.c +index b22fc90..4e54afb 100644 +--- a/src/option.c ++++ b/src/option.c +@@ -2347,7 +2347,9 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + char *star; + new->next = daemon->synth_domains; + daemon->synth_domains = new; +- if ((star = strrchr(new->prefix, '*')) && *(star+1) == 0) ++ if (new->prefix && ++ (star = strrchr(new->prefix, '*')) ++ && *(star+1) == 0) + { + *star = 0; + new->indexed = 1; +-- +2.41.0 + diff --git a/SOURCES/dnsmasq-2.90-CVE-2023-50387-CVE-2023-50868.patch b/SOURCES/dnsmasq-2.90-CVE-2023-50387-CVE-2023-50868.patch new file mode 100644 index 0000000..816c96e --- /dev/null +++ b/SOURCES/dnsmasq-2.90-CVE-2023-50387-CVE-2023-50868.patch @@ -0,0 +1,2318 @@ +commit 5eab3c6f13562230f1eeab3788585f73ffbf469d +Author: Tomas Korbar +Date: Wed Feb 28 16:51:17 2024 +0100 + + Fix CVE 2023-50387 and CVE 2023-50868 + + Add limits on the resources used to do DNSSEC validation. + DNSSEC introduces a potential CPU DoS, because a crafted domain + can force a validator to a large number of cryptographic + operations whilst attempting to do validation. When using TCP + transport a DNSKEY RRset contain thousands of members and any + RRset can have thousands of signatures. The potential number + of signature validations to follow the RFC for validation + for one RRset is the cross product of the keys and signatures, + so millions. In practice, the actual numbers are much lower, + so attacks can be mitigated by limiting the amount of + cryptographic "work" to a much lower amount. The actual + limits are number a signature validation fails per RRset(20), + number of signature validations and hash computations + per query(200), number of sub-queries to fetch DS and DNSKEY + RRsets per query(40), and the number of iterations in a + NSEC3 record(150). These values are sensible, but there is, as yet, + no standardisation on the values for a "conforming" domain, so a + new option --dnssec-limit is provided should they need to be altered. + The algorithm to validate DS records has also been altered to reduce + the maximum work from cross product of the number of DS records and + number of DNSKEYs to the cross product of the number of DS records + and supported DS digest types. As the number of DS digest types + is in single figures, this reduces the exposure. + + Credit is due to Elias Heftrig, Haya Schulmann, Niklas Vogel, + and Michael Waidner from the German National Research Center for + Applied Cybersecurity ATHENE for finding this vulnerability. + + CVE 2023-50387 and CVE 2023-50868 apply. + Note that the is a security vulnerablity only when DNSSEC validation + is enabled. + +diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 +index d80cad5..db94a99 100644 +--- a/man/dnsmasq.8 ++++ b/man/dnsmasq.8 +@@ -762,6 +762,15 @@ Copy the DNSSEC Authenticated Data bit from upstream servers to downstream clien + alternative to having dnsmasq validate DNSSEC, but it depends on the security of the network between + dnsmasq and the upstream servers, and the trustworthiness of the upstream servers. + .TP ++.B --dnssec-limits=[,.......] ++Override the default resource limits applied to DNSSEC validation. Cryptographic operations are expensive and crafted domains ++can DoS a DNSSEC validator by forcing it to do hundreds of thousands of such operations. To avoid this, the dnsmasq validation code ++applies limits on how much work will be expended in validation. If any of the limits are exceeded, the validation will fail and the ++domain treated as BOGUS. There are four limits, in order(default values in parens): number a signature validation fails per RRset(20), number of signature validations and ++hash computations per query(200), number of sub-queries to fetch DS and DNSKEY RRsets per query(40), and the number of iterations in a NSEC3 record(150). ++The maximum values reached during validation are stored, and dumped as part of the stats generated by SIGUSR1. Supplying a limit value of 0 leaves the default in place, so ++\fB--dnssec-limits=0,0,20\fP sets the number of sub-queries to 20 whilst leaving the other limits at default values. ++.TP + .B --dnssec-debug + Set debugging mode for the DNSSEC validation, set the Checking Disabled bit on upstream queries, + and don't convert replies which do not validate to responses with +diff --git a/src/config.h b/src/config.h +index 3878ba4..8d7bd79 100644 +--- a/src/config.h ++++ b/src/config.h +@@ -22,7 +22,10 @@ + #define EDNS_PKTSZ 1232 /* default max EDNS.0 UDP packet from from /dnsflagday.net/2020 */ + #define SAFE_PKTSZ 1280 /* "go anywhere" UDP packet size */ + #define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */ +-#define DNSSEC_WORK 50 /* Max number of queries to validate one question */ ++#define DNSSEC_LIMIT_WORK 40 /* Max number of queries to validate one question */ ++#define DNSSEC_LIMIT_SIG_FAIL 20 /* Number of signature that can fail to validate in one answer */ ++#define DNSSEC_LIMIT_CRYPTO 200 /* max no. of crypto operations to validate one query. */ ++#define DNSSEC_LIMIT_NSEC3_ITERS 150 /* Max. number if iterations allowed in NSEC3 record. */ + #define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */ + #define FORWARD_TEST 50 /* try all servers every 50 queries */ + #define FORWARD_TIME 20 /* or 20 seconds */ +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index 711ffd3..cf1c9c4 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -626,15 +626,30 @@ struct hostsfile { + + + /* DNSSEC status values. */ +-#define STAT_SECURE 1 +-#define STAT_INSECURE 2 +-#define STAT_BOGUS 3 +-#define STAT_NEED_DS 4 +-#define STAT_NEED_KEY 5 +-#define STAT_TRUNCATED 6 +-#define STAT_SECURE_WILDCARD 7 +-#define STAT_OK 8 +-#define STAT_ABANDONED 9 ++#define STAT_SECURE 0x10000 ++#define STAT_INSECURE 0x20000 ++#define STAT_BOGUS 0x30000 ++#define STAT_NEED_DS 0x40000 ++#define STAT_NEED_KEY 0x50000 ++#define STAT_TRUNCATED 0x60000 ++#define STAT_SECURE_WILDCARD 0x70000 ++#define STAT_OK 0x80000 ++#define STAT_ABANDONED 0x90000 ++ ++#define DNSSEC_FAIL_NYV 0x0001 /* key not yet valid */ ++#define DNSSEC_FAIL_EXP 0x0002 /* key expired */ ++#define DNSSEC_FAIL_INDET 0x0004 /* indetermined */ ++#define DNSSEC_FAIL_NOKEYSUP 0x0008 /* no supported key algo. */ ++#define DNSSEC_FAIL_NOSIG 0x0010 /* No RRsigs */ ++#define DNSSEC_FAIL_NOZONE 0x0020 /* No Zone bit set */ ++#define DNSSEC_FAIL_NONSEC 0x0040 /* No NSEC */ ++#define DNSSEC_FAIL_NODSSUP 0x0080 /* no supported DS algo. */ ++#define DNSSEC_FAIL_NOKEY 0x0100 /* no DNSKEY */ ++#define DNSSEC_FAIL_NSEC3_ITERS 0x0200 /* too many iterations in NSEC3 */ ++#define DNSSEC_FAIL_BADPACKET 0x0400 /* bad packet */ ++#define DNSSEC_FAIL_WORK 0x0800 /* too much crypto */ ++ ++#define STAT_ISEQUAL(a, b) (((a) & 0xffff0000) == (b)) + + #define FREC_NOREBIND 1 + #define FREC_CHECKING_DISABLED 2 +@@ -666,7 +681,7 @@ struct frec { + time_t time; + unsigned char *hash[HASH_SIZE]; + #ifdef HAVE_DNSSEC +- int class, work_counter; ++ int class, work_counter, validate_counter; + struct blockdata *stash; /* Saved reply, whilst we validate */ + size_t stash_len; + struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */ +@@ -702,6 +717,12 @@ struct frec { + #define LEASE_TA 64 /* IPv6 temporary lease */ + #define LEASE_HAVE_HWADDR 128 /* Have set hwaddress */ + ++#define LIMIT_SIG_FAIL 0 ++#define LIMIT_CRYPTO 1 ++#define LIMIT_WORK 2 ++#define LIMIT_NSEC3_ITERS 3 ++#define LIMIT_MAX 4 ++ + struct dhcp_lease { + int clid_len; /* length of client identifier */ + unsigned char *clid; /* clientid */ +@@ -1055,6 +1076,7 @@ extern struct daemon { + struct ds_config *ds; + int dnssec_no_time_check; + int back_to_the_future; ++ int limit[LIMIT_MAX]; + char *timestamp_file; + #endif + +@@ -1210,10 +1232,12 @@ int in_zone(struct auth_zone *zone, char *name, char **cut); + + /* dnssec.c */ + size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, union mysockaddr *addr, int edns_pktsz); +-int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); +-int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); ++int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, ++ char *keyname, int class, int *validate_count); ++int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, ++ char *keyname, int class, int *validate_count); + int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, +- int check_unsigned, int *neganswer, int *nons); ++ int check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_count); + int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen); + size_t filter_rrsigs(struct dns_header *header, size_t plen); + int setup_timestamp(void); +@@ -1602,7 +1626,7 @@ int do_poll(int timeout); + + /* rrfilter.c */ + size_t rrfilter(struct dns_header *header, size_t plen, int mode); +-u16 *rrfilter_desc(int type); ++short *rrfilter_desc(int type); + int expand_workspace(unsigned char ***wkspc, int *szp, int new); + + /* edns0.c */ +diff --git a/src/dnssec.c b/src/dnssec.c +index b2dda1b..fd6e722 100644 +--- a/src/dnssec.c ++++ b/src/dnssec.c +@@ -186,10 +186,8 @@ int setup_timestamp(void) + } + + /* Check whether today/now is between date_start and date_end */ +-static int check_date_range(u32 date_start, u32 date_end) ++static int is_check_date(unsigned long curtime) + { +- unsigned long curtime = time(0); +- + /* Checking timestamps may be temporarily disabled */ + + /* If the current time if _before_ the timestamp +@@ -211,15 +209,10 @@ static int check_date_range(u32 date_start, u32 date_end) + queue_event(EVENT_RELOAD); /* purge cache */ + } + +- if (daemon->back_to_the_future == 0) +- return 1; ++ return daemon->back_to_the_future; + } +- else if (daemon->dnssec_no_time_check) +- return 1; +- +- /* We must explicitly check against wanted values, because of SERIAL_UNDEF */ +- return serial_compare_32(curtime, date_start) == SERIAL_GT +- && serial_compare_32(curtime, date_end) == SERIAL_LT; ++ else ++ return !daemon->dnssec_no_time_check; + } + + /* Return bytes of canonicalised rrdata one by one. +@@ -232,7 +225,7 @@ static int check_date_range(u32 date_start, u32 date_end) + On returning 0, the end has been reached. + */ + struct rdata_state { +- u16 *desc; ++ short *desc; + size_t c; + unsigned char *end, *ip, *op; + char *buff; +@@ -253,7 +246,7 @@ static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state + { + d = *(state->desc); + +- if (d == (u16)-1) ++ if (d == -1) + { + /* all the bytes to the end. */ + if ((state->c = state->end - state->ip) != 0) +@@ -301,7 +294,7 @@ static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state + + /* Bubble sort the RRset into the canonical order. */ + +-static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, ++static int sort_rrset(struct dns_header *header, size_t plen, short *rr_desc, int rrsetidx, + unsigned char **rrset, char *buff1, char *buff2) + { + int swap, i, j; +@@ -333,37 +326,64 @@ static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int + if (!CHECK_LEN(header, state2.ip, plen, rdlen2)) + return rrsetidx; /* short packet */ + state2.end = state2.ip + rdlen2; +- +- while (1) ++ ++ /* If the RR has no names in it then canonicalisation ++ is the identity function and we can compare ++ the RRs directly. If not we compare the ++ canonicalised RRs one byte at a time. */ ++ if (*rr_desc == -1) + { +- int ok1, ok2; ++ int rdmin = rdlen1 > rdlen2 ? rdlen2 : rdlen1; ++ int cmp = memcmp(state1.ip, state2.ip, rdmin); + +- ok1 = get_rdata(header, plen, &state1); +- ok2 = get_rdata(header, plen, &state2); +- +- if (!ok1 && !ok2) ++ if (cmp > 0 || (cmp == 0 && rdlen1 > rdmin)) ++ { ++ unsigned char *tmp = rrset[i+1]; ++ rrset[i+1] = rrset[i]; ++ rrset[i] = tmp; ++ swap = 1; ++ } ++ else if (cmp == 0 && (rdlen1 == rdlen2)) + { + /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */ + for (j = i+1; j < rrsetidx-1; j++) + rrset[j] = rrset[j+1]; + rrsetidx--; + i--; +- break; + } +- else if (ok1 && (!ok2 || *state1.op > *state2.op)) +- { +- unsigned char *tmp = rrset[i+1]; +- rrset[i+1] = rrset[i]; +- rrset[i] = tmp; +- swap = 1; +- break; +- } +- else if (ok2 && (!ok1 || *state2.op > *state1.op)) +- break; +- +- /* arrive here when bytes are equal, go round the loop again +- and compare the next ones. */ + } ++ else ++ /* Comparing canonicalised RRs, byte-at-a-time. */ ++ while (1) ++ { ++ int ok1, ok2; ++ ++ ok1 = get_rdata(header, plen, &state1); ++ ok2 = get_rdata(header, plen, &state2); ++ ++ if (!ok1 && !ok2) ++ { ++ /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */ ++ for (j = i+1; j < rrsetidx-1; j++) ++ rrset[j] = rrset[j+1]; ++ rrsetidx--; ++ i--; ++ break; ++ } ++ else if (ok1 && (!ok2 || *state1.op > *state2.op)) ++ { ++ unsigned char *tmp = rrset[i+1]; ++ rrset[i+1] = rrset[i]; ++ rrset[i] = tmp; ++ swap = 1; ++ break; ++ } ++ else if (ok2 && (!ok1 || *state2.op > *state1.op)) ++ break; ++ ++ /* arrive here when bytes are equal, go round the loop again ++ and compare the next ones. */ ++ } + } + } while (swap); + +@@ -383,7 +403,7 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int + int gotkey = 0; + + if (!(p = skip_questions(header, plen))) +- return STAT_BOGUS; ++ return 0; + + /* look for RRSIGs for this RRset and get pointers to each RR in the set. */ + for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); +@@ -395,14 +415,14 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int + pstart = p; + + if (!(res = extract_name(header, plen, &p, name, 0, 10))) +- return STAT_BOGUS; /* bad packet */ ++ return 0; /* bad packet */ + + GETSHORT(stype, p); + GETSHORT(sclass, p); +- p += 4; /* TTL */ +- ++ + pdata = p; + ++ p += 4; /* TTL */ + GETSHORT(rdlen, p); + + if (!CHECK_LEN(header, p, plen, rdlen)) +@@ -465,7 +485,7 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int + sigs[sigidx++] = pdata; + } + +- p = pdata + 2; /* restore for ADD_RDLEN */ ++ p = pdata + 6; /* restore for ADD_RDLEN */ + } + } + +@@ -479,12 +499,24 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int + return 1; + } + ++int dec_counter(int *counter, char *message) ++{ ++ if ((*counter)-- == 0) ++ { ++ my_syslog(LOG_WARNING, "limit exceeded: %s", message ? message : _("per-query crypto work")); ++ return 1; ++ } ++ ++ return 0; ++} ++ + /* Validate a single RRset (class, type, name) in the supplied DNS reply + Return code: + STAT_SECURE if it validates. + STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion. + (In this case *wildcard_out points to the "body" of the wildcard within name.) + STAT_BOGUS signature is wrong, bad packet. ++ STAT_ABANDONED validation abandoned do to excess resource usage. + STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname) + STAT_NEED_DS need DS to complete validation (name is returned in keyname) + +@@ -494,16 +526,23 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int + Name is unchanged on exit. keyname is used as workspace and trashed. + + Call explore_rrset first to find and count RRs and sigs. ++ ++ ttl_out is the floor on TTL, based on TTL and orig_ttl and expiration of sig used to validate. + */ + static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx, +- char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in) ++ char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, ++ int algo_in, int keytag_in, unsigned long *ttl_out, int *validate_counter) + { + unsigned char *p; +- int rdlen, j, name_labels, algo, labels, orig_ttl, key_tag; ++ int rdlen, j, name_labels, algo, labels, key_tag, sig_fail_cnt; + struct crec *crecp = NULL; +- u16 *rr_desc = rrfilter_desc(type); +- u32 sig_expiration, sig_inception +-; ++ short *rr_desc = rrfilter_desc(type); ++ u32 sig_expiration, sig_inception; ++ int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP | DNSSEC_FAIL_NOKEYSUP; ++ ++ unsigned long curtime = time(0); ++ int time_check = is_check_date(curtime); ++ + if (wildcard_out) + *wildcard_out = NULL; + +@@ -515,16 +554,19 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in + rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname); + + /* Now try all the sigs to try and find one which validates */ +- for (j = 0; j limit[LIMIT_SIG_FAIL], j = 0; j = 18 checked previously */ + psav = p; + +@@ -539,16 +581,47 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in + if (!extract_name(header, plen, &p, keyname, 1, 0)) + return STAT_BOGUS; + +- if (!check_date_range(sig_inception, sig_expiration) || +- labels > name_labels || +- !(hash = hash_find(algo_digest_name(algo))) || ++ if (!time_check) ++ failflags &= ~(DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP); ++ else ++ { ++ /* We must explicitly check against wanted values, because of SERIAL_UNDEF */ ++ if (serial_compare_32(curtime, sig_inception) == SERIAL_LT) ++ continue; ++ else ++ failflags &= ~DNSSEC_FAIL_NYV; ++ ++ if (serial_compare_32(curtime, sig_expiration) == SERIAL_GT) ++ continue; ++ else ++ failflags &= ~DNSSEC_FAIL_EXP; ++ } ++ ++ if (!(hash = hash_find(algo_digest_name(algo)))) ++ continue; ++ else ++ failflags &= ~DNSSEC_FAIL_NOKEYSUP; ++ ++ if (labels > name_labels || + !hash_init(hash, &ctx, &digest)) + continue; + + /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */ + if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY))) + return STAT_NEED_KEY; +- ++ ++ if (ttl_out) ++ { ++ /* 4035 5.3.3 rules on TTLs */ ++ if (orig_ttl < ttl) ++ ttl = orig_ttl; ++ ++ if (time_check && difftime(sig_expiration, curtime) < ttl) ++ ttl = difftime(sig_expiration, curtime); ++ ++ *ttl_out = ttl; ++ } ++ + sig = p; + sig_len = rdlen - (p - psav); + +@@ -559,7 +632,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in + hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname); + from_wire(keyname); + +-#define RRBUFLEN 300 /* Most RRs are smaller than this. */ ++#define RRBUFLEN 128 /* Most RRs are smaller than this. */ + + for (i = 0; i < rrsetidx; ++i) + { +@@ -597,63 +670,85 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in + hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name_start); + hash->update(ctx, 4, p); /* class and type */ + hash->update(ctx, 4, (unsigned char *)&nsigttl); +- +- p += 8; /* skip class, type, ttl */ ++ ++ p += 8; /* skip type, class, ttl */ + GETSHORT(rdlen, p); + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_BOGUS; +- +- /* canonicalise rdata and calculate length of same, use +- name buffer as workspace for get_rdata. */ +- state.ip = p; +- state.op = NULL; +- state.desc = rr_desc; +- state.buff = name; +- state.end = p + rdlen; +- +- for (j = 0; get_rdata(header, plen, &state); j++) +- if (j < RRBUFLEN) +- rrbuf[j] = *state.op; + +- len = htons((u16)j); +- hash->update(ctx, 2, (unsigned char *)&len); +- +- /* If the RR is shorter than RRBUFLEN (most of them, in practice) +- then we can just digest it now. If it exceeds RRBUFLEN we have to +- go back to the start and do it in chunks. */ +- if (j >= RRBUFLEN) ++ /* Optimisation for RR types which need no cannonicalisation. ++ This includes DNSKEY DS NSEC and NSEC3, which are also long, so ++ it saves lots of calls to get_rdata, and avoids the pessimal ++ segmented insertion, even with a small rrbuf[]. ++ ++ If canonicalisation is not needed, a simple insertion into the hash works. ++ */ ++ if (*rr_desc == -1) ++ { ++ len = htons(rdlen); ++ hash->update(ctx, 2, (unsigned char *)&len); ++ hash->update(ctx, rdlen, p); ++ } ++ else + { ++ /* canonicalise rdata and calculate length of same, use ++ name buffer as workspace for get_rdata. */ + state.ip = p; + state.op = NULL; + state.desc = rr_desc; +- ++ state.buff = name; ++ state.end = p + rdlen; ++ + for (j = 0; get_rdata(header, plen, &state); j++) ++ if (j < RRBUFLEN) ++ rrbuf[j] = *state.op; ++ ++ len = htons((u16)j); ++ hash->update(ctx, 2, (unsigned char *)&len); ++ ++ /* If the RR is shorter than RRBUFLEN (most of them, in practice) ++ then we can just digest it now. If it exceeds RRBUFLEN we have to ++ go back to the start and do it in chunks. */ ++ if (j >= RRBUFLEN) + { +- rrbuf[j] = *state.op; +- +- if (j == RRBUFLEN - 1) +- { +- hash->update(ctx, RRBUFLEN, rrbuf); +- j = -1; +- } ++ state.ip = p; ++ state.op = NULL; ++ state.desc = rr_desc; ++ ++ for (j = 0; get_rdata(header, plen, &state); j++) ++ { ++ rrbuf[j] = *state.op; ++ ++ if (j == RRBUFLEN - 1) ++ { ++ hash->update(ctx, RRBUFLEN, rrbuf); ++ j = -1; ++ } ++ } + } ++ ++ if (j != 0) ++ hash->update(ctx, j, rrbuf); + } +- +- if (j != 0) +- hash->update(ctx, j, rrbuf); + } + + hash->digest(ctx, hash->digest_size, digest); + + /* namebuff used for workspace above, restore to leave unchanged on exit */ + p = (unsigned char*)(rrset[0]); +- extract_name(header, plen, &p, name, 1, 0); ++ if (!extract_name(header, plen, &p, name, 1, 0)) ++ return STAT_BOGUS; + + if (key) + { +- if (algo_in == algo && keytag_in == key_tag && +- verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo)) +- return STAT_SECURE; ++ if (algo_in == algo && keytag_in == key_tag) ++ { ++ if (dec_counter(validate_counter, NULL)) ++ return STAT_ABANDONED; ++ ++ if (verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo)) ++ return STAT_SECURE; ++ } + } + else + { +@@ -661,13 +756,25 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in + for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY)) + if (crecp->addr.key.algo == algo && + crecp->addr.key.keytag == key_tag && +- crecp->uid == (unsigned int)class && +- verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo)) +- return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE; ++ crecp->uid == (unsigned int)class) ++ { ++ if (dec_counter(validate_counter, NULL)) ++ return STAT_ABANDONED; ++ ++ if (verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo)) ++ return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE; ++ ++ /* An attacker can waste a lot of our CPU by setting up a giant DNSKEY RRSET full of failing ++ keys, all of which we have to try. Since many failing keys is not likely for ++ a legitimate domain, set a limit on how many can fail. */ ++ if (dec_counter(&sig_fail_cnt, _("per-RRSet signature fails"))) ++ return STAT_ABANDONED; ++ } + } + } + +- return STAT_BOGUS; ++ /* If we reach this point, no verifying key was found */ ++ return STAT_BOGUS | failflags | DNSSEC_FAIL_NOKEY; + } + + +@@ -677,36 +784,45 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in + STAT_OK Done, key(s) in cache. + STAT_BOGUS No DNSKEYs found, which can be validated with DS, + or self-sign for DNSKEY RRset is not valid, bad packet. ++ STAT_ABANDONED resource exhaustion. + STAT_NEED_DS DS records to validate a key not found, name in keyname +- STAT_NEED_KEY DNSKEY records to validate a key not found, name in keyname + */ +-int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) ++int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, ++ char *keyname, int class, int *validate_counter) + { +- unsigned char *psave, *p = (unsigned char *)(header+1); ++ unsigned char *psave, *p = (unsigned char *)(header+1), *keyaddr; + struct crec *crecp, *recp1; +- int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag; +- struct blockdata *key; ++ int rc, j, qtype, qclass, rdlen, flags, algo, keytag, sigcnt, rrcnt; ++ unsigned long ttl, sig_ttl; + struct all_addr a; ++ int failflags = DNSSEC_FAIL_NODSSUP | DNSSEC_FAIL_NOZONE; ++ char valid_digest[255]; ++ static unsigned char **cached_digest; ++ static size_t cached_digest_size = 0; + +- if (ntohs(header->qdcount) != 1 || +- !extract_name(header, plen, &p, name, 1, 4)) +- return STAT_BOGUS; ++ if (ntohs(header->qdcount) != 1 || RCODE(header) != NOERROR || !extract_name(header, plen, &p, name, 1, 4)) ++ return STAT_BOGUS | DNSSEC_FAIL_NOKEY; + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + +- if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0) +- return STAT_BOGUS; ++ if (qtype != T_DNSKEY || qclass != class || ++ !explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) || ++ rrcnt == 0) ++ return STAT_BOGUS | DNSSEC_FAIL_NOKEY; + ++ if (sigcnt == 0) ++ return STAT_BOGUS | DNSSEC_FAIL_NOSIG; ++ + /* See if we have cached a DS record which validates this key */ + if (!(crecp = cache_find_by_name(NULL, name, now, F_DS))) + { + strcpy(keyname, name); + return STAT_NEED_DS; + } +- ++ + /* NOTE, we need to find ONE DNSKEY which matches the DS */ +- for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--) ++ for (j = ntohs(header->ancount); j != 0; j--) + { + /* Ensure we have type, class TTL and length */ + if (!(rc = extract_name(header, plen, &p, name, 0, 10))) +@@ -717,7 +833,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch + GETLONG(ttl, p); + GETSHORT(rdlen, p); + +- if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4) ++ if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_BOGUS; /* bad packet */ + + if (qclass != class || qtype != T_DNSKEY || rc == 2) +@@ -725,171 +841,233 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch + p += rdlen; + continue; + } +- ++ ++ if (rdlen < 5) ++ return STAT_BOGUS; /* min 1 byte key! */ ++ + psave = p; + + GETSHORT(flags, p); + if (*p++ != 3) +- return STAT_BOGUS; +- algo = *p++; +- keytag = dnskey_keytag(algo, flags, p, rdlen - 4); +- key = NULL; +- +- /* key must have zone key flag set */ +- if (flags & 0x100) +- key = blockdata_alloc((char*)p, rdlen - 4); +- +- p = psave; +- +- if (!ADD_RDLEN(header, p, plen, rdlen)) + { +- if (key) +- blockdata_free(key); +- return STAT_BOGUS; /* bad packet */ ++ p = psave + rdlen; ++ continue; + } ++ algo = *p++; ++ keyaddr = p; ++ keytag = dnskey_keytag(algo, flags, keyaddr, rdlen - 4); ++ ++ p = psave + rdlen; + +- /* No zone key flag or malloc failure */ +- if (!key) ++ /* key must have zone key flag set */ ++ if (!(flags & 0x100)) + continue; + ++ failflags &= ~DNSSEC_FAIL_NOZONE; ++ ++ /* clear digest cache. */ ++ memset(valid_digest, 0, sizeof(valid_digest)); ++ + for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS)) + { + void *ctx; + unsigned char *digest, *ds_digest; + const struct nettle_hash *hash; +- int sigcnt, rrcnt; ++ int wire_len; ++ ++ if ((recp1->flags & F_NEG) || ++ recp1->addr.ds.algo != algo || ++ recp1->addr.ds.keytag != keytag || ++ recp1->uid != (unsigned int)class) ++ continue; ++ ++ if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest)))) ++ continue; ++ ++ failflags &= ~DNSSEC_FAIL_NODSSUP; ++ ++ if (recp1->addr.ds.keylen != (int)hash->digest_size || ++ !(ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL))) ++ continue; + +- if (recp1->addr.ds.algo == algo && +- recp1->addr.ds.keytag == keytag && +- recp1->uid == (unsigned int)class && +- (hash = hash_find(ds_digest_name(recp1->addr.ds.digest))) && +- hash_init(hash, &ctx, &digest)) +- ++ if (valid_digest[recp1->addr.ds.digest]) ++ digest = cached_digest[recp1->addr.ds.digest]; ++ else + { +- int wire_len = to_wire(name); ++ /* computing a hash is a unit of crypto work. */ ++ if (dec_counter(validate_counter, NULL)) ++ return STAT_ABANDONED; ++ ++ if (!hash_init(hash, &ctx, &digest)) ++ continue; ++ ++ wire_len = to_wire(name); + + /* Note that digest may be different between DSs, so +- we can't move this outside the loop. */ ++ we can't move this outside the loop. We keep ++ copies of each digest we make for this key, ++ so maximum digest work is O(keys x digests_types) ++ rather then O(keys x DSs) */ + hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name); + hash->update(ctx, (unsigned int)rdlen, psave); + hash->digest(ctx, hash->digest_size, digest); + + from_wire(name); +- +- if (!(recp1->flags & F_NEG) && +- recp1->addr.ds.keylen == (int)hash->digest_size && +- (ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) && +- memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 && +- explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) && +- sigcnt != 0 && rrcnt != 0 && +- validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, +- NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE) ++ ++ if (recp1->addr.ds.digest >= cached_digest_size) + { +- valid = 1; +- break; ++ unsigned char **new; ++ ++ /* whine_malloc zeros memory */ ++ if ((new = whine_malloc((recp1->addr.ds.digest + 5) * sizeof(unsigned char *)))) ++ { ++ if (cached_digest_size != 0) ++ { ++ memcpy(new, cached_digest, cached_digest_size * sizeof(unsigned char *)); ++ free(cached_digest); ++ } ++ ++ cached_digest_size = recp1->addr.ds.digest + 5; ++ cached_digest = new; ++ } ++ } ++ ++ if (recp1->addr.ds.digest < cached_digest_size) ++ { ++ if (!cached_digest[recp1->addr.ds.digest]) ++ cached_digest[recp1->addr.ds.digest] = whine_malloc(recp1->addr.ds.keylen); ++ ++ if (cached_digest[recp1->addr.ds.digest]) ++ { ++ memcpy(cached_digest[recp1->addr.ds.digest], digest, recp1->addr.ds.keylen); ++ valid_digest[recp1->addr.ds.digest] = 1; ++ } + } + } +- } +- blockdata_free(key); +- } +- +- if (valid) +- { +- /* DNSKEY RRset determined to be OK, now cache it. */ +- cache_start_insert(); +- +- p = skip_questions(header, plen); +- +- for (j = ntohs(header->ancount); j != 0; j--) +- { +- /* Ensure we have type, class TTL and length */ +- if (!(rc = extract_name(header, plen, &p, name, 0, 10))) +- return STAT_BOGUS; /* bad packet */ + +- GETSHORT(qtype, p); +- GETSHORT(qclass, p); +- GETLONG(ttl, p); +- GETSHORT(rdlen, p); +- +- if (!CHECK_LEN(header, p, plen, rdlen)) +- return STAT_BOGUS; /* bad packet */ +- +- if (qclass == class && rc == 1) ++ if (memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0) + { +- psave = p; ++ /* Found the key validated by a DS record. ++ Now check the self-sig for the entire key RRset using that key. ++ Note that validate_rrset() will never return STAT_NEED_KEY here, ++ since we supply the key it will use as an argument. */ ++ struct blockdata *key; ++ ++ if (!(key = blockdata_alloc((char *)keyaddr, rdlen - 4))) ++ break; ++ ++ rc = validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, ++ NULL, key, rdlen - 4, algo, keytag, &sig_ttl, validate_counter); ++ ++ blockdata_free(key); ++ ++ if (STAT_ISEQUAL(rc, STAT_ABANDONED)) ++ return rc; ++ ++ /* can't validate KEY RRset with this key, see if there's another that ++ will, which is validated by another DS. */ ++ if (!STAT_ISEQUAL(rc, STAT_SECURE)) ++ break; + +- if (qtype == T_DNSKEY) ++ /* DNSKEY RRset determined to be OK, now cache it. */ ++ cache_start_insert(); ++ ++ p = skip_questions(header, plen); ++ ++ for (j = ntohs(header->ancount); j != 0; j--) + { +- if (rdlen < 4) ++ /* Ensure we have type, class TTL and length */ ++ if (!(rc = extract_name(header, plen, &p, name, 0, 10))) + return STAT_BOGUS; /* bad packet */ + +- GETSHORT(flags, p); +- if (*p++ != 3) +- return STAT_BOGUS; +- algo = *p++; +- keytag = dnskey_keytag(algo, flags, p, rdlen - 4); ++ GETSHORT(qtype, p); ++ GETSHORT(qclass, p); ++ GETLONG(ttl, p); ++ GETSHORT(rdlen, p); + +- /* Cache needs to known class for DNSSEC stuff */ +- a.addr.dnssec.class = class; ++ /* TTL may be limited by sig. */ ++ if (sig_ttl < ttl) ++ ttl = sig_ttl; + +- if ((key = blockdata_alloc((char*)p, rdlen - 4))) +- { +- if (!(recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK))) +- { +- blockdata_free(key); +- return STAT_BOGUS; +- } +- else +- { +- a.addr.log.keytag = keytag; +- a.addr.log.algo = algo; +- if (algo_digest_name(algo)) +- log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu"); +- else +- log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)"); +- +- recp1->addr.key.keylen = rdlen - 4; +- recp1->addr.key.keydata = key; +- recp1->addr.key.algo = algo; +- recp1->addr.key.keytag = keytag; +- recp1->addr.key.flags = flags; +- } +- } ++ if (!CHECK_LEN(header, p, plen, rdlen)) ++ return STAT_BOGUS; /* bad packet */ ++ ++ psave = p; ++ ++ if (qclass == class && rc == 1 && qtype == T_DNSKEY) ++ { ++ if (rdlen < 4) ++ return STAT_BOGUS; /* min 1 byte key! */ ++ ++ GETSHORT(flags, p); ++ if (*p++ == 3) ++ { ++ algo = *p++; ++ keytag = dnskey_keytag(algo, flags, p, rdlen - 4); ++ ++ /* Cache needs to known class for DNSSEC stuff */ ++ a.addr.dnssec.class = class; ++ ++ if (!(key = blockdata_alloc((char*)p, rdlen - 4))) ++ return STAT_BOGUS; ++ ++ if (!(recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK))) ++ { ++ blockdata_free(key); ++ return STAT_BOGUS; ++ } ++ ++ a.addr.log.keytag = keytag; ++ a.addr.log.algo = algo; ++ if (algo_digest_name(algo)) ++ log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu"); ++ else ++ log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)"); ++ ++ recp1->addr.key.keylen = rdlen - 4; ++ recp1->addr.key.keydata = key; ++ recp1->addr.key.algo = algo; ++ recp1->addr.key.keytag = keytag; ++ recp1->addr.key.flags = flags; ++ } ++ } ++ ++ p = psave + rdlen; + } +- +- p = psave; ++ ++ /* commit cache insert. */ ++ cache_end_insert(); ++ return STAT_OK; + } +- +- if (!ADD_RDLEN(header, p, plen, rdlen)) +- return STAT_BOGUS; /* bad packet */ + } +- +- /* commit cache insert. */ +- cache_end_insert(); +- return STAT_OK; + } +- ++ + log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY"); +- return STAT_BOGUS; ++ return STAT_BOGUS | failflags; + } + + /* The DNS packet is expected to contain the answer to a DS query +- Put all DSs in the answer which are valid into the cache. ++ Put all DSs in the answer which are valid and have hash and signature algos ++ we support into the cache. + Also handles replies which prove that there's no DS at this location, + either because the zone is unsigned or this isn't a zone cut. These are + cached too. ++ If none of the DS's are for supported algos, treat the answer as if ++ it's a proof of no DS at this location. RFC4035 para 5.2. + return codes: + STAT_OK At least one valid DS found and in cache. + STAT_BOGUS no DS in reply or not signed, fails validation, bad packet. + STAT_NEED_KEY DNSKEY records to validate a DS not found, name in keyname + STAT_NEED_DS DS record needed. ++ STAT_ABANDONED resource exhaustion. + */ + +-int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) ++int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, ++ char *keyname, int class, int *validate_counter) + { + unsigned char *p = (unsigned char *)(header+1); +- int qtype, qclass, rc, i, neganswer, nons; +- int aclass, atype, rdlen; ++ int qtype, qclass, rc, i, neganswer = 0, nons = 0, servfail = 0, neg_ttl = 0, found_supported = 0; ++ int aclass, atype, rdlen, flags; + unsigned long ttl; + struct all_addr a; + +@@ -901,35 +1079,51 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char + GETSHORT(qclass, p); + + if (qtype != T_DS || qclass != class) +- rc = STAT_BOGUS; ++ return STAT_BOGUS; ++ ++ /* A SERVFAIL answer has been seen to a DS query not at start of authority, ++ so treat it as such and continue to search for a DS or proof of no existence ++ further down the tree. */ ++ if (RCODE(header) == SERVFAIL) ++ servfail = neganswer = nons = 1; + else +- rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons); ++ { ++ rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl, validate_counter); + +- if (rc == STAT_INSECURE) +- rc = STAT_BOGUS; +- +- p = (unsigned char *)(header+1); +- extract_name(header, plen, &p, name, 1, 4); +- p += 4; /* qtype, qclass */ ++ if (STAT_ISEQUAL(rc, STAT_INSECURE)) ++ { ++ my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name); ++ log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure"); ++ return STAT_BOGUS | DNSSEC_FAIL_INDET; ++ } ++ ++ p = (unsigned char *)(header+1); ++ if (!extract_name(header, plen, &p, name, 1, 4)) ++ return STAT_BOGUS; ++ ++ p += 4; /* qtype, qclass */ ++ ++ /* If the key needed to validate the DS is on the same domain as the DS, we'll ++ loop getting nowhere. Stop that now. This can happen of the DS answer comes ++ from the DS's zone, and not the parent zone. */ ++ if (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname)) ++ { ++ log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS"); ++ return STAT_BOGUS; ++ } + +- /* If the key needed to validate the DS is on the same domain as the DS, we'll +- loop getting nowhere. Stop that now. This can happen of the DS answer comes +- from the DS's zone, and not the parent zone. */ +- if (rc == STAT_BOGUS || (rc == STAT_NEED_KEY && hostname_isequal(name, keyname))) +- { +- log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS"); +- return STAT_BOGUS; ++ if (!STAT_ISEQUAL(rc, STAT_SECURE)) ++ return rc; + } + +- if (rc != STAT_SECURE) +- return rc; +- + if (!neganswer) + { + cache_start_insert(); + + for (i = 0; i < ntohs(header->ancount); i++) + { ++ unsigned char *psave; ++ + if (!(rc = extract_name(header, plen, &p, name, 0, 10))) + return STAT_BOGUS; /* bad packet */ + +@@ -940,16 +1134,16 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char + + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_BOGUS; /* bad packet */ ++ ++ psave = p; + + if (aclass == class && atype == T_DS && rc == 1) + { + int algo, digest, keytag; +- unsigned char *psave = p; + struct blockdata *key; +- struct crec *crecp; +- +- if (rdlen < 4) +- return STAT_BOGUS; /* bad packet */ ++ ++ if (rdlen < 5) ++ return STAT_BOGUS; /* min 1 byte digest! */ + + GETSHORT(keytag, p); + algo = *p++; +@@ -957,9 +1151,19 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char + + /* Cache needs to known class for DNSSEC stuff */ + a.addr.dnssec.class = class; +- +- if ((key = blockdata_alloc((char*)p, rdlen - 4))) ++ ++ if (!ds_digest_name(digest) || !algo_digest_name(algo)) ++ { ++ a.addr.log.keytag = keytag; ++ a.addr.log.algo = algo; ++ a.addr.log.digest = digest; ++ log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS for keytag %hu, algo %hu, digest %hu (not supported)"); ++ neg_ttl = ttl; ++ } ++ else if ((key = blockdata_alloc((char*)p, rdlen - 4))) + { ++ struct crec *crecp; ++ + if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))) + { + blockdata_free(key); +@@ -967,39 +1171,36 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char + } + else + { +- a.addr.log.keytag = keytag; ++ a.addr.log.keytag = keytag; + a.addr.log.algo = algo; + a.addr.log.digest = digest; +- if (ds_digest_name(digest) && algo_digest_name(algo)) +- log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu"); +- else +- log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu (not supported)"); +- +- crecp->addr.ds.digest = digest; +- crecp->addr.ds.keydata = key; +- crecp->addr.ds.algo = algo; +- crecp->addr.ds.keytag = keytag; +- crecp->addr.ds.keylen = rdlen - 4; ++ log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS for keytag %hu, algo %hu, digest %hu"); ++ ++ crecp->addr.ds.digest = digest; ++ crecp->addr.ds.keydata = key; ++ crecp->addr.ds.algo = algo; ++ crecp->addr.ds.keytag = keytag; ++ crecp->addr.ds.keylen = rdlen - 4; ++ ++ found_supported = 1; + } + } +- +- p = psave; + } +- if (!ADD_RDLEN(header, p, plen, rdlen)) +- return STAT_BOGUS; /* bad packet */ ++ ++ p = psave + rdlen; + } + + cache_end_insert(); + ++ /* Fall through if no supported algo DS found. */ ++ if (found_supported) ++ return STAT_OK; + } +- else ++ ++ flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK; ++ ++ if (neganswer) + { +- int flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK; +- unsigned long minttl = ULONG_MAX; +- +- if (!(p = skip_section(p, ntohs(header->ancount), header, plen))) +- return STAT_BOGUS; +- + if (RCODE(header) == NXDOMAIN) + flags |= F_NXDOMAIN; + +@@ -1007,57 +1208,21 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char + to store presence/absence of NS. */ + if (nons) + flags &= ~F_DNSSECOK; +- +- for (i = ntohs(header->nscount); i != 0; i--) +- { +- if (!(p = skip_name(p, header, plen, 0))) +- return STAT_BOGUS; +- +- GETSHORT(atype, p); +- GETSHORT(aclass, p); +- GETLONG(ttl, p); +- GETSHORT(rdlen, p); +- +- if (!CHECK_LEN(header, p, plen, rdlen)) +- return STAT_BOGUS; /* bad packet */ +- +- if (aclass != class || atype != T_SOA) +- { +- p += rdlen; +- continue; +- } +- +- if (ttl < minttl) +- minttl = ttl; +- +- /* MNAME */ +- if (!(p = skip_name(p, header, plen, 0))) +- return STAT_BOGUS; +- /* RNAME */ +- if (!(p = skip_name(p, header, plen, 20))) +- return STAT_BOGUS; +- p += 16; /* SERIAL REFRESH RETRY EXPIRE */ +- +- GETLONG(ttl, p); /* minTTL */ +- if (ttl < minttl) +- minttl = ttl; +- +- break; +- } +- +- if (i != 0) +- { +- cache_start_insert(); +- +- a.addr.dnssec.class = class; +- if (!cache_insert(name, &a, now, ttl, flags)) +- return STAT_BOGUS; +- +- cache_end_insert(); +- +- log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "no DS"); +- } + } ++ ++ cache_start_insert(); ++ ++ a.addr.dnssec.class = class; ++ ++ /* Use TTL from NSEC for negative cache entries */ ++ if (!cache_insert(name, NULL, now, neg_ttl, flags)) ++ return STAT_BOGUS; ++ ++ cache_end_insert(); ++ ++ if (neganswer) ++ log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, ++ servfail ? "SERVFAIL" : (nons ? "no DS/cut" : "no DS")); + + return STAT_OK; + } +@@ -1127,6 +1292,7 @@ static int hostname_cmp(const char *a, const char *b) + } + } + ++/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */ + static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count, + char *workspace1_in, char *workspace2, char *name, int type, int *nons) + { +@@ -1146,12 +1312,12 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi + + p = nsecs[i]; + if (!extract_name(header, plen, &p, workspace1, 1, 10)) +- return 0; ++ return DNSSEC_FAIL_BADPACKET; + p += 8; /* class, type, TTL */ + GETSHORT(rdlen, p); + psave = p; +- if (!extract_name(header, plen, &p, workspace2, 1, 10)) +- return 0; ++ if (!extract_name(header, plen, &p, workspace2, 1, 0)) ++ return DNSSEC_FAIL_BADPACKET; + + /* If NSEC comes from wildcard expansion, use original wildcard + as name for computation. */ +@@ -1179,12 +1345,13 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi + { + /* 4035 para 5.4. Last sentence */ + if (type == T_NSEC || type == T_RRSIG) +- return 1; ++ return 0; + + /* NSEC with the same name as the RR we're testing, check + that the type in question doesn't appear in the type map */ + rdlen -= p - psave; +- /* rdlen is now length of type map, and p points to it */ ++ /* rdlen is now length of type map, and p points to it ++ packet checked to be as long as rdlen implies in prove_non_existence() */ + + /* If we can prove that there's no NS record, return that information. */ + if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0) +@@ -1195,25 +1362,25 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi + /* A CNAME answer would also be valid, so if there's a CNAME is should + have been returned. */ + if ((p[2] & (0x80 >> T_CNAME)) != 0) +- return 0; ++ return DNSSEC_FAIL_NONSEC; + + /* If the SOA bit is set for a DS record, then we have the + DS from the wrong side of the delegation. For the root DS, + this is expected. */ + if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0) +- return 0; ++ return DNSSEC_FAIL_NONSEC; + } + + while (rdlen >= 2) + { + if (!CHECK_LEN(header, p, plen, rdlen)) +- return 0; ++ return DNSSEC_FAIL_BADPACKET; + + if (p[0] == type >> 8) + { + /* Does the NSEC say our type exists? */ + if (offset < p[1] && (p[offset+2] & mask) != 0) +- return 0; ++ return DNSSEC_FAIL_NONSEC; + + break; /* finished checking */ + } +@@ -1222,24 +1389,24 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi + p += p[1]; + } + +- return 1; ++ return 0; + } + else if (rc == -1) + { + /* Normal case, name falls between NSEC name and next domain name, + wrap around case, name falls between NSEC name (rc == -1) and end */ + if (hostname_cmp(workspace2, name) >= 0 || hostname_cmp(workspace1, workspace2) >= 0) +- return 1; ++ return 0; + } + else + { + /* wrap around case, name falls between start and next domain name */ + if (hostname_cmp(workspace1, workspace2) >= 0 && hostname_cmp(workspace2, name) >=0 ) +- return 1; ++ return 0; + } + } + +- return 0; ++ return DNSSEC_FAIL_NONSEC; + } + + /* return digest length, or zero on error */ +@@ -1313,23 +1480,23 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige + for (i = 0; i < nsec_count; i++) + if ((p = nsecs[i])) + { +- if (!extract_name(header, plen, &p, workspace1, 1, 0) || ++ if (!extract_name(header, plen, &p, workspace1, 1, 10) || + !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) + return 0; + + p += 8; /* class, type, TTL */ + GETSHORT(rdlen, p); ++ + psave = p; ++ ++ /* packet checked to be as long as implied by rdlen, salt_len and hash_len in prove_non_existence() */ + p++; /* algo */ + flags = *p++; /* flags */ + p += 2; /* iterations */ + salt_len = *p++; /* salt_len */ + p += salt_len; /* salt */ + hash_len = *p++; /* p now points to next hashed name */ +- +- if (!CHECK_LEN(header, p, plen, hash_len)) +- return 0; +- ++ + if (digest_len == base32_len && hash_len == base32_len) + { + int rc = memcmp(workspace2, digest, digest_len); +@@ -1337,7 +1504,8 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige + if (rc == 0) + { + /* We found an NSEC3 whose hashed name exactly matches the query, so +- we just need to check the type map. p points to the RR data for the record. */ ++ we just need to check the type map. p points to the RR data for the record. ++ Note we have packet length up to rdlen bytes checked. */ + + int offset = (type & 0xff) >> 3; + int mask = 0x80 >> (type & 0x07); +@@ -1345,15 +1513,12 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige + p += hash_len; /* skip next-domain hash */ + rdlen -= p - psave; + +- if (!CHECK_LEN(header, p, plen, rdlen)) +- return 0; +- + if (rdlen >= 2 && p[0] == 0) + { + /* If we can prove that there's no NS record, return that information. */ + if (nons && (p[2] & (0x80 >> T_NS)) != 0) + *nons = 0; +- ++ + /* A CNAME answer would also be valid, so if there's a CNAME is should + have been returned. */ + if ((p[2] & (0x80 >> T_CNAME)) != 0) +@@ -1412,8 +1577,9 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige + return 0; + } + +-static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, +- char *workspace1, char *workspace2, char *name, int type, char *wildname, int *nons) ++/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */ ++static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, char *workspace1, ++ char *workspace2, char *name, int type, char *wildname, int *nons, int *validate_counter) + { + unsigned char *salt, *p, *digest; + int digest_len, i, iterations, salt_len, base32_len, algo = 0; +@@ -1433,7 +1599,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns + for (i = 0; i < nsec_count; i++) + { + if (!(p = skip_name(nsecs[i], header, plen, 15))) +- return 0; /* bad packet */ ++ return DNSSEC_FAIL_BADPACKET; /* bad packet */ + + p += 10; /* type, class, TTL, rdlen */ + algo = *p++; +@@ -1444,23 +1610,18 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns + + /* No usable NSEC3s */ + if (i == nsec_count) +- return 0; ++ return DNSSEC_FAIL_NONSEC; + + p++; /* flags */ + + GETSHORT (iterations, p); +- /* Upper-bound iterations, to avoid DoS. +- Strictly, there are lower bounds for small keys, but +- since we don't have key size info here, at least limit +- to the largest bound, for 4096-bit keys. RFC 5155 10.3 */ +- if (iterations > 2500) +- return 0; ++ /* Upper-bound iterations, to avoid DoS. RFC 9276 refers. */ ++ if (iterations > daemon->limit[LIMIT_NSEC3_ITERS]) ++ return DNSSEC_FAIL_NSEC3_ITERS; + + salt_len = *p++; + salt = p; +- if (!CHECK_LEN(header, salt, plen, salt_len)) +- return 0; /* bad packet */ +- ++ + /* Now prune so we only have NSEC3 records with same iterations, salt and algo */ + for (i = 0; i < nsec_count; i++) + { +@@ -1470,7 +1631,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns + nsecs[i] = NULL; /* Speculative, will be restored if OK. */ + + if (!(p = skip_name(nsec3p, header, plen, 15))) +- return 0; /* bad packet */ ++ return DNSSEC_FAIL_BADPACKET; /* bad packet */ + + p += 10; /* type, class, TTL, rdlen */ + +@@ -1490,9 +1651,6 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns + if (salt_len != *p++) + continue; + +- if (!CHECK_LEN(header, p, plen, salt_len)) +- return 0; /* bad packet */ +- + if (memcmp(p, salt, salt_len) != 0) + continue; + +@@ -1500,11 +1658,14 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns + nsecs[i] = nsec3p; + } + ++ if (dec_counter(validate_counter, NULL)) ++ return DNSSEC_FAIL_WORK; ++ + if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0) +- return 0; ++ return DNSSEC_FAIL_NONSEC; + + if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons, count_labels(name))) +- return 1; ++ return 0; + + /* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3" + or an answer inferred from a wildcard record. */ +@@ -1519,15 +1680,20 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns + if (wildname && hostname_isequal(closest_encloser, wildname)) + break; + ++ if (dec_counter(validate_counter, NULL)) ++ return DNSSEC_FAIL_WORK; ++ + if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0) +- return 0; ++ return DNSSEC_FAIL_NONSEC; + + for (i = 0; i < nsec_count; i++) + if ((p = nsecs[i])) + { +- if (!extract_name(header, plen, &p, workspace1, 1, 0) || +- !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) +- return 0; ++ if (!extract_name(header, plen, &p, workspace1, 1, 0)) ++ return DNSSEC_FAIL_BADPACKET; ++ ++ if (!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) ++ return DNSSEC_FAIL_NONSEC; + + if (digest_len == base32_len && + memcmp(digest, workspace2, digest_len) == 0) +@@ -1542,35 +1708,43 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns + while ((closest_encloser = strchr(closest_encloser, '.'))); + + if (!closest_encloser || !next_closest) +- return 0; ++ return DNSSEC_FAIL_NONSEC; + + /* Look for NSEC3 that proves the non-existence of the next-closest encloser */ ++ if (dec_counter(validate_counter, NULL)) ++ return DNSSEC_FAIL_WORK; ++ + if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0) +- return 0; ++ return DNSSEC_FAIL_NONSEC; + + if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1)) +- return 0; ++ return DNSSEC_FAIL_NONSEC; + + /* Finally, check that there's no seat of wildcard synthesis */ + if (!wildname) + { + if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest) +- return 0; ++ return DNSSEC_FAIL_NONSEC; + + wildcard--; + *wildcard = '*'; + ++ if (dec_counter(validate_counter, NULL)) ++ return DNSSEC_FAIL_WORK; ++ + if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0) +- return 0; ++ return DNSSEC_FAIL_NONSEC; + + if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1)) +- return 0; ++ return DNSSEC_FAIL_NONSEC; + } + +- return 1; ++ return 0; + } + +-static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons) ++/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */ ++static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, ++ char *wildname, int *nons, int *nsec_ttl, int *validate_counter) + { + static unsigned char **nsecset = NULL, **rrsig_labels = NULL; + static int nsecset_sz = 0, rrsig_labels_sz = 0; +@@ -1578,35 +1752,47 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key + int type_found = 0; + unsigned char *auth_start, *p = skip_questions(header, plen); + int type, class, rdlen, i, nsecs_found; ++ unsigned long ttl; + + /* Move to NS section */ + if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen))) +- return 0; ++ return DNSSEC_FAIL_BADPACKET; + + auth_start = p; + +- for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--) ++ for (nsecs_found = 0, i = 0; i < ntohs(header->nscount); i++) + { + unsigned char *pstart = p; + + if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10)) +- return 0; ++ return DNSSEC_FAIL_BADPACKET; + + GETSHORT(type, p); + GETSHORT(class, p); +- p += 4; /* TTL */ ++ GETLONG(ttl, p); + GETSHORT(rdlen, p); +- ++ ++ if (!CHECK_LEN(header, p, plen, rdlen)) ++ return DNSSEC_FAIL_BADPACKET; ++ + if (class == qclass && (type == T_NSEC || type == T_NSEC3)) + { ++ if (nsec_ttl) ++ { ++ /* Limit TTL with sig TTL */ ++ if (daemon->rr_status[ntohs(header->ancount) + i] < ttl) ++ ttl = daemon->rr_status[ntohs(header->ancount) + i]; ++ *nsec_ttl = ttl; ++ } ++ + /* No mixed NSECing 'round here, thankyouverymuch */ + if (type_found != 0 && type_found != type) +- return 0; ++ return DNSSEC_FAIL_NONSEC; + + type_found = type; + + if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found)) +- return 0; ++ return DNSSEC_FAIL_BADPACKET; + + if (type == T_NSEC) + { +@@ -1621,30 +1807,33 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key + int res, j, rdlen1, type1, class1; + + if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found)) +- return 0; ++ return DNSSEC_FAIL_BADPACKET; + + rrsig_labels[nsecs_found] = NULL; + + for (j = ntohs(header->nscount); j != 0; j--) + { +- if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10))) +- return 0; ++ unsigned char *psav; + ++ if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10))) ++ return DNSSEC_FAIL_BADPACKET; ++ + GETSHORT(type1, p1); + GETSHORT(class1, p1); + p1 += 4; /* TTL */ + GETSHORT(rdlen1, p1); + ++ psav = p1; ++ + if (!CHECK_LEN(header, p1, plen, rdlen1)) +- return 0; ++ return DNSSEC_FAIL_BADPACKET; + + if (res == 1 && class1 == qclass && type1 == T_RRSIG) + { + int type_covered; +- unsigned char *psav = p1; +- ++ + if (rdlen1 < 18) +- return 0; /* bad packet */ ++ return DNSSEC_FAIL_BADPACKET; /* bad packet */ + + GETSHORT(type_covered, p1); + +@@ -1656,33 +1845,56 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key + if (!rrsig_labels[nsecs_found]) + rrsig_labels[nsecs_found] = p1; + else if (*rrsig_labels[nsecs_found] != *p1) /* algo */ +- return 0; +- } +- p1 = psav; ++ return DNSSEC_FAIL_NONSEC; ++ } + } + +- if (!ADD_RDLEN(header, p1, plen, rdlen1)) +- return 0; ++ p1 = psav + rdlen1; + } + + /* Must have found at least one sig. */ + if (!rrsig_labels[nsecs_found]) +- return 0; ++ return DNSSEC_FAIL_NONSEC; ++ } ++ else if (type == T_NSEC3) ++ { ++ /* Decode the packet structure enough to check that rdlen is big enough ++ to contain everything other than the type bitmap. ++ (packet checked to be long enough to contain rdlen above) ++ We don't need to do any further length checks in check_nes3_coverage() ++ or prove_non_existence_nsec3() */ ++ ++ int salt_len, hash_len; ++ unsigned char *psav = p; ++ ++ if (rdlen < 5) ++ return DNSSEC_FAIL_BADPACKET; ++ ++ p += 4; /* algo, flags, iterations */ ++ salt_len = *p++; /* salt_len */ ++ if (rdlen < (6 + salt_len)) ++ return DNSSEC_FAIL_BADPACKET; /* check up to hash_length */ ++ ++ p += salt_len; /* salt */ ++ hash_len = *p++; ++ if (rdlen < (6 + salt_len + hash_len)) ++ return DNSSEC_FAIL_BADPACKET; /* check to end of next hashed name */ ++ ++ p = psav; + } + + nsecset[nsecs_found++] = pstart; + } + +- if (!ADD_RDLEN(header, p, plen, rdlen)) +- return 0; ++ p += rdlen; + } + + if (type_found == T_NSEC) + return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons); + else if (type_found == T_NSEC3) +- return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons); ++ return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons, validate_counter); + else +- return 0; ++ return DNSSEC_FAIL_NONSEC; + } + + /* Check signing status of name. +@@ -1777,35 +1989,40 @@ static int zone_status(char *name, int class, char *keyname, time_t now) + STAT_BOGUS signature is wrong, bad packet, no validation where there should be. + STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class) + STAT_NEED_DS need DS to complete validation (name is returned in keyname) ++ STAT_ABANDONED resource exhaustion. + + daemon->rr_status points to a char array which corressponds to the RRs in the +- answer section (only). This is set to 1 for each RR which is validated, and 0 for any which aren't. ++ answer and auth sections. This is set to >1 for each RR which is validated, and 0 for any which aren't. ++ ++ When validating replies to DS records, we're only interested in the NSEC{3} RRs in the auth section. ++ Other RRs in that section missing sigs will not cause am INSECURE reply. We determine this mode ++ if the nons argument is non-NULL. + */ + int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, +- int *class, int check_unsigned, int *neganswer, int *nons) ++ int *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_counter) + { + static unsigned char **targets = NULL; + static int target_sz = 0; + + unsigned char *ans_start, *p1, *p2; + int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx; +- int i, j, rc; ++ int i, j, rc = STAT_INSECURE; + int secure = STAT_SECURE; +- ++ int rc_nsec; + /* extend rr_status if necessary */ +- if (daemon->rr_status_sz < ntohs(header->ancount)) ++ if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount)) + { +- char *new = whine_malloc(ntohs(header->ancount) + 64); ++ unsigned long *new = whine_malloc(sizeof(*daemon->rr_status) * (ntohs(header->ancount) + ntohs(header->nscount) + 64)); + + if (!new) + return STAT_BOGUS; + + free(daemon->rr_status); +- daemon->rr_status = new; +- daemon->rr_status_sz = ntohs(header->ancount) + 64; ++ daemon->rr_status = (char *)new; ++ daemon->rr_status_sz = ntohs(header->ancount) + ntohs(header->nscount) + 64; + } + +- memset(daemon->rr_status, 0, ntohs(header->ancount)); ++ memset(daemon->rr_status, 0, sizeof(*daemon->rr_status) * daemon->rr_status_sz); + + if (neganswer) + *neganswer = 0; +@@ -1820,7 +2037,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + + /* Find all the targets we're looking for answers to. + The zeroth array element is for the query, subsequent ones +- for CNAME targets, unless the query is for a CNAME. */ ++ for CNAME targets, unless the query is for a CNAME or ANY. */ + + if (!expand_workspace(&targets, &target_sz, 0)) + return STAT_BOGUS; +@@ -1839,7 +2056,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + if (qtype == T_RRSIG) + return STAT_INSECURE; + +- if (qtype != T_CNAME) ++ if (qtype != T_CNAME && qtype != T_ANY) + for (j = ntohs(header->ancount); j != 0; j--) + { + if (!(p1 = skip_name(p1, header, plen, 10))) +@@ -1863,10 +2080,10 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + + for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++) + { +- if (i != 0 && !ADD_RDLEN(header, p1, plen, rdlen1)) +- return STAT_BOGUS; +- +- if (!extract_name(header, plen, &p1, name, 1, 10)) ++ if (i != 0 && !ADD_RDLEN(header, p1, plen, rdlen1)) ++ return STAT_BOGUS; ++ ++ if (!extract_name(header, plen, &p1, name, 1, 10)) + return STAT_BOGUS; /* bad packet */ + + GETSHORT(type1, p1); +@@ -1896,12 +2113,9 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + return STAT_BOGUS; + } + ++ /* Done already: copy the validation status */ + if (j != i) +- { +- /* Done already: copy the validation status */ +- if (i < ntohs(header->ancount)) +- daemon->rr_status[i] = daemon->rr_status[j]; +- } ++ daemon->rr_status[i] = daemon->rr_status[j]; + else + { + /* Not done, validate now */ +@@ -1914,19 +2128,32 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + /* No signatures for RRset. We can be configured to assume this is OK and return an INSECURE result. */ + if (sigcnt == 0) + { +- if (check_unsigned) ++ /* NSEC and NSEC3 records must be signed. We make this assumption elsewhere. */ ++ if (type1 == T_NSEC || type1 == T_NSEC3) ++ return STAT_BOGUS | DNSSEC_FAIL_NOSIG; ++ else if (nons && i >= ntohs(header->ancount)) ++ /* If we're validating a DS reply, rather than looking for the value of AD bit, ++ we only care that NSEC and NSEC3 RRs in the auth section are signed. ++ Return SECURE even if others (SOA....) are not. */ ++ rc = STAT_SECURE; ++ else + { +- rc = zone_status(name, class1, keyname, now); +- if (rc == STAT_SECURE) +- rc = STAT_BOGUS; +- if (class) +- *class = class1; /* Class for NEED_DS or NEED_KEY */ ++ /* unsigned RRsets in auth section are not BOGUS, but do make reply insecure. */ ++ if (check_unsigned && i < ntohs(header->ancount)) ++ { ++ rc = zone_status(name, class1, keyname, now); ++ if (STAT_ISEQUAL(rc, STAT_SECURE)) ++ rc = STAT_BOGUS | DNSSEC_FAIL_NOSIG; ++ ++ if (class) ++ *class = class1; /* Class for NEED_DS or NEED_KEY */ ++ } ++ else ++ rc = STAT_INSECURE; ++ ++ if (!STAT_ISEQUAL(rc, STAT_INSECURE)) ++ return rc; + } +- else +- rc = STAT_INSECURE; +- +- if (rc != STAT_INSECURE) +- return rc; + } + else + { +@@ -1935,21 +2162,21 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + strcpy(daemon->workspacename, keyname); + rc = zone_status(daemon->workspacename, class1, keyname, now); + +- if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) ++ if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS)) + { +- /* Zone is insecure, don't need to validate RRset */ + if (class) + *class = class1; /* Class for NEED_DS or NEED_KEY */ + return rc; + } + + /* Zone is insecure, don't need to validate RRset */ +- if (rc == STAT_SECURE) ++ if (STAT_ISEQUAL(rc, STAT_SECURE)) + { ++ unsigned long sig_ttl; + rc = validate_rrset(now, header, plen, class1, type1, sigcnt, +- rrcnt, name, keyname, &wildname, NULL, 0, 0, 0); ++ rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl, validate_counter); + +- if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) ++ if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS) || STAT_ISEQUAL(rc, STAT_ABANDONED)) + { + if (class) + *class = class1; /* Class for DS or DNSKEY */ +@@ -1959,8 +2186,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + /* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */ + + /* Note that RR is validated */ +- if (i < ntohs(header->ancount)) +- daemon->rr_status[i] = 1; ++ daemon->rr_status[i] = sig_ttl; + + /* Note if we've validated either the answer to the question + or the target of a CNAME. Any not noted will need NSEC or +@@ -1983,16 +2209,16 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + Note that we may not yet have validated the NSEC/NSEC3 RRsets. + That's not a problem since if the RRsets later fail + we'll return BOGUS then. */ +- if (rc == STAT_SECURE_WILDCARD && +- !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL)) +- return STAT_BOGUS; ++ if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) && ++ ((rc_nsec = prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL, validate_counter))) != 0) ++ return (rc_nsec & DNSSEC_FAIL_WORK) ? STAT_ABANDONED : (STAT_BOGUS | rc_nsec); + + rc = STAT_SECURE; + } + } + } + +- if (rc == STAT_INSECURE) ++ if (STAT_ISEQUAL(rc, STAT_INSECURE)) + secure = STAT_INSECURE; + } + +@@ -2002,28 +2228,32 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + { + if (neganswer) + *neganswer = 1; +- ++ + if (!extract_name(header, plen, &p2, name, 1, 10)) + return STAT_BOGUS; /* bad packet */ +- ++ + /* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */ +- ++ + /* For anything other than a DS record, this situation is OK if either + the answer is in an unsigned zone, or there's a NSEC records. */ +- if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons)) ++ if ((rc_nsec = prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl, validate_counter)) != 0) + { ++ if (rc_nsec & DNSSEC_FAIL_WORK) ++ return STAT_ABANDONED; ++ + /* Empty DS without NSECS */ + if (qtype == T_DS) +- return STAT_BOGUS; ++ return STAT_BOGUS | rc_nsec; + +- if ((rc = zone_status(name, qclass, keyname, now)) != STAT_SECURE) ++ if ((rc_nsec & (DNSSEC_FAIL_NONSEC | DNSSEC_FAIL_NSEC3_ITERS)) && ++ !STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE)) + { + if (class) + *class = qclass; /* Class for NEED_DS or NEED_KEY */ + return rc; + } + +- return STAT_BOGUS; /* signed zone, no NSECs */ ++ return STAT_BOGUS | rc_nsec; /* signed zone, no NSECs */ + } + } + +diff --git a/src/forward.c b/src/forward.c +index 989fb61..64124b1 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -406,7 +406,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + if (ad_reqd) + forward->flags |= FREC_AD_QUESTION; + #ifdef HAVE_DNSSEC +- forward->work_counter = DNSSEC_WORK; ++ forward->work_counter = daemon->limit[LIMIT_WORK]; ++ forward->validate_counter = daemon->limit[LIMIT_CRYPTO]; + if (do_bit) + forward->flags |= FREC_DO_QUESTION; + #endif +@@ -770,6 +771,7 @@ void reply_query(int fd, time_t now) + struct server *server; + void *hash; + struct server_domain *sd; ++ size_t plen; + + /* packet buffer overwritten */ + daemon->srv_save = NULL; +@@ -820,7 +822,6 @@ void reply_query(int fd, time_t now) + /* for broken servers, attempt to send to another one. */ + { + unsigned char *pheader; +- size_t plen; + int is_sign; + + /* In strict order mode, there must be a server later in the chain +@@ -926,29 +927,38 @@ void reply_query(int fd, time_t now) + */ + if (header->hb3 & HB3_TC) + status = STAT_TRUNCATED; ++ struct frec *orig; ++ int log_resource = 0; + + while (1) + { + /* As soon as anything returns BOGUS, we stop and unwind, to do otherwise + would invite infinite loops, since the answers to DNSKEY and DS queries + will not be cached, so they'll be repeated. */ ++ /* Find the original query that started it all.... */ ++ for (orig = forward; orig->dependent; orig = orig->dependent); ++ + if (status != STAT_BOGUS && status != STAT_TRUNCATED && status != STAT_ABANDONED) + { + if (forward->flags & FREC_DNSKEY_QUERY) +- status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); ++ status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter); + else if (forward->flags & FREC_DS_QUERY) +- status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); ++ status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter); + else + status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, + option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC), +- NULL, NULL); ++ NULL, NULL, NULL, &orig->validate_counter); + } ++ status = status & 0xffff0000; ++ ++ if (status == STAT_ABANDONED) ++ log_resource = 1; + + /* Can't validate, as we're missing key data. Put this + answer aside, whilst we get that. */ + if (status == STAT_NEED_DS || status == STAT_NEED_KEY) + { +- struct frec *new, *orig; ++ struct frec *new; + + /* Free any saved query */ + if (forward->stash) +@@ -959,11 +969,11 @@ void reply_query(int fd, time_t now) + return; + forward->stash_len = n; + +- /* Find the original query that started it all.... */ +- for (orig = forward; orig->dependent; orig = orig->dependent); +- +- if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1))) ++ if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1))) { + status = STAT_ABANDONED; ++ my_syslog(LOG_WARNING, _("limit exceeded: per-query subqueries")); ++ log_resource = 1; ++ } + else + { + int fd, type = SERV_DO_DNSSEC; +@@ -1056,7 +1066,15 @@ void reply_query(int fd, time_t now) + } + return; + } +- ++ if (log_resource) ++ { ++ /* Log the actual validation that made us barf. */ ++ unsigned char *p = (unsigned char *)(header+1); ++ if (extract_name(header, plen, &p, daemon->namebuff, 0, 4) == 1) ++ my_syslog(LOG_WARNING, _("validation of %s failed: resource limit exceeded."), ++ daemon->namebuff[0] ? daemon->namebuff : "."); ++ } ++ + /* Validated original answer, all done. */ + if (!forward->dependent) + break; +@@ -1499,7 +1517,7 @@ void receive_query(struct listener *listen, time_t now) + /* Recurse up the key hierarchy */ + static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, + int class, char *name, char *keyname, struct server *server, +- int have_mark, unsigned int mark, int *keycount) ++ int have_mark, unsigned int mark, int *keycount, int *validatecount) + { + int new_status; + unsigned char *packet = NULL; +@@ -1516,20 +1534,35 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si + struct server *firstsendto = NULL; + + /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */ +- if (--(*keycount) == 0) +- new_status = STAT_ABANDONED; +- else if (status == STAT_NEED_KEY) +- new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class); ++ if (status == STAT_NEED_KEY) ++ new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class, validatecount); + else if (status == STAT_NEED_DS) +- new_status = dnssec_validate_ds(now, header, n, name, keyname, class); ++ new_status = dnssec_validate_ds(now, header, n, name, keyname, class, validatecount); + else + new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, +- option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC), +- NULL, NULL); +- +- if (new_status != STAT_NEED_DS && new_status != STAT_NEED_KEY) ++ !option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC), ++ NULL, NULL, NULL, validatecount); ++ new_status = new_status & 0xffff0000; ++ ++ if (new_status != STAT_NEED_DS && new_status != STAT_NEED_KEY && new_status != STAT_ABANDONED) + break; + ++ if ((*keycount)-- == 0) ++ { ++ my_syslog(LOG_WARNING, _("limit exceeded: per-query subqueries")); ++ new_status = STAT_ABANDONED; ++ } ++ ++ if (new_status == STAT_ABANDONED) ++ { ++ /* Log the actual validation that made us barf. */ ++ unsigned char *p = (unsigned char *)(header+1); ++ if (extract_name(header, n, &p, daemon->namebuff, 0, 4) == 1) ++ my_syslog(LOG_WARNING, _("validation of %s failed: resource limit exceeded."), ++ daemon->namebuff[0] ? daemon->namebuff : "."); ++ break; ++ } ++ + /* Can't validate because we need a key/DS whose name now in keyname. + Make query for same, and recurse to validate */ + if (!packet) +@@ -1624,7 +1657,7 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si + server->flags |= SERV_GOT_TCP; + + m = (c1 << 8) | c2; +- new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount); ++ new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount, validatecount); + break; + } + +@@ -1947,9 +1980,10 @@ unsigned char *tcp_request(int confd, time_t now, + #ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled && (last_server->flags & SERV_DO_DNSSEC)) + { +- int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */ ++ int keycount = daemon->limit[LIMIT_WORK]; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */ ++ int validatecount = daemon->limit[LIMIT_CRYPTO]; + int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname, +- last_server, have_mark, mark, &keycount); ++ last_server, have_mark, mark, &keycount, &validatecount); + char *result, *domain = "result"; + + if (status == STAT_ABANDONED) +diff --git a/src/option.c b/src/option.c +index 4e54afb..944a482 100644 +--- a/src/option.c ++++ b/src/option.c +@@ -160,6 +160,7 @@ struct myoption { + #define LOPT_DHCPTTL 348 + #define LOPT_TFTP_MTU 349 + #define LOPT_REPLY_DELAY 350 ++#define LOPT_DNSSEC_LIMITS 351 + + #ifdef HAVE_GETOPT_LONG + static const struct option opts[] = +@@ -313,6 +314,7 @@ static const struct myoption opts[] = + { "dnssec-check-unsigned", 0, 0, LOPT_DNSSEC_CHECK }, + { "dnssec-no-timecheck", 0, 0, LOPT_DNSSEC_TIME }, + { "dnssec-timestamp", 1, 0, LOPT_DNSSEC_STAMP }, ++ { "dnssec-limits", 1, 0, LOPT_DNSSEC_LIMITS }, + #ifdef OPTION6_PREFIX_CLASS + { "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS }, + #endif +@@ -485,6 +487,7 @@ static struct { + { LOPT_DNSSEC_CHECK, OPT_DNSSEC_NO_SIGN, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL }, + { LOPT_DNSSEC_TIME, OPT_DNSSEC_TIME, NULL, gettext_noop("Don't check DNSSEC signature timestamps until first cache-reload"), NULL }, + { LOPT_DNSSEC_STAMP, ARG_ONE, "", gettext_noop("Timestamp file to verify system clock for DNSSEC"), NULL }, ++ { LOPT_DNSSEC_LIMITS, ARG_ONE, ",..", gettext_noop("Set resource limits for DNSSEC validation"), NULL }, + #ifdef OPTION6_PREFIX_CLASS + { LOPT_PREF_CLSS, ARG_DUP, "set:tag,", gettext_noop("Specify DHCPv6 prefix class"), NULL }, + #endif +@@ -4353,6 +4356,23 @@ err: + } + + #ifdef HAVE_DNSSEC ++ case LOPT_DNSSEC_LIMITS: ++ { ++ int lim, val; ++ ++ for (lim = LIMIT_SIG_FAIL; arg && lim < LIMIT_MAX ; lim++, arg = comma) ++ { ++ comma = split(arg); ++ ++ if (!atoi_check(arg, &val)) ++ ret_err(gen_err); ++ ++ if (val != 0) ++ daemon->limit[lim] = val; ++ } ++ ++ break; ++ } + case LOPT_DNSSEC_STAMP: + daemon->timestamp_file = opt_string_alloc(arg); + break; +@@ -4903,6 +4923,12 @@ void read_opts(int argc, char **argv, char *compile_opts) + daemon->soa_refresh = SOA_REFRESH; + daemon->soa_retry = SOA_RETRY; + daemon->soa_expiry = SOA_EXPIRY; ++#ifdef HAVE_DNSSEC ++ daemon->limit[LIMIT_SIG_FAIL] = DNSSEC_LIMIT_SIG_FAIL; ++ daemon->limit[LIMIT_CRYPTO] = DNSSEC_LIMIT_CRYPTO; ++ daemon->limit[LIMIT_WORK] = DNSSEC_LIMIT_WORK; ++ daemon->limit[LIMIT_NSEC3_ITERS] = DNSSEC_LIMIT_NSEC3_ITERS; ++#endif + daemon->max_port = MAX_PORT; + daemon->min_port = MIN_PORT; + +diff --git a/src/rrfilter.c b/src/rrfilter.c +index f5b9c61..b332f29 100644 +--- a/src/rrfilter.c ++++ b/src/rrfilter.c +@@ -136,9 +136,9 @@ static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, i + + if (class == C_IN) + { +- u16 *d; ++ short *d; + +- for (pp = p, d = rrfilter_desc(type); *d != (u16)-1; d++) ++ for (pp = p, d = rrfilter_desc(type); *d != -1; d++) + { + if (*d != 0) + pp += *d; +@@ -266,7 +266,7 @@ size_t rrfilter(struct dns_header *header, size_t plen, int mode) + } + + /* This is used in the DNSSEC code too, hence it's exported */ +-u16 *rrfilter_desc(int type) ++short *rrfilter_desc(int type) + { + /* List of RRtypes which include domains in the data. + 0 -> domain +@@ -277,7 +277,7 @@ u16 *rrfilter_desc(int type) + anything which needs no mangling. + */ + +- static u16 rr_desc[] = ++ static short rr_desc[] = + { + T_NS, 0, -1, + T_MD, 0, -1, +@@ -302,10 +302,10 @@ u16 *rrfilter_desc(int type) + 0, -1 /* wildcard/catchall */ + }; + +- u16 *p = rr_desc; ++ short *p = rr_desc; + + while (*p != type && *p != 0) +- while (*p++ != (u16)-1); ++ while (*p++ != -1); + + return p+1; + } diff --git a/SPECS/dnsmasq.spec b/SPECS/dnsmasq.spec index 3c75ec1..db69e1a 100644 --- a/SPECS/dnsmasq.spec +++ b/SPECS/dnsmasq.spec @@ -13,7 +13,7 @@ Name: dnsmasq Version: 2.79 -Release: 31%{?extraversion:.%{extraversion}}%{?dist} +Release: 31%{?extraversion:.%{extraversion}}%{?dist}.2 Summary: A lightweight DHCP/caching DNS server License: GPLv2 or GPLv3 @@ -96,6 +96,10 @@ Patch43: dnsmasq-2.87-log-root-writeable.patch # Downstream only patch; https://bugzilla.redhat.com/show_bug.cgi?id=2209031 # complements patch42 Patch44: dnsmasq-2.85-domain-blocklist-speedup.patch +# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=dd33e98da09c487a58b6cb6693b8628c0b234a3b +Patch45: dnsmasq-2.80-synth-domain-RHEL-15216.patch +# https://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=214a046f47b9f7dd56f5eef3a8678ccbd6e973b7 +Patch46: dnsmasq-2.90-CVE-2023-50387-CVE-2023-50868.patch # This is workaround to nettle bug #1549190 # https://bugzilla.redhat.com/show_bug.cgi?id=1549190 @@ -173,6 +177,8 @@ server's leases. %patch42 -p1 -b .rh2186481-2 %patch43 -p1 -b .rh2156789 %patch44 -p1 -b .rh2209031 +%patch45 -p1 -b .RHEL-15216 +%patch46 -p1 -b .CVE-2023-50868-CVE-2023-50387 # use /var/lib/dnsmasq instead of /var/lib/misc for file in dnsmasq.conf.example man/dnsmasq.8 man/es/dnsmasq.8 src/config.h; do @@ -272,6 +278,14 @@ install -Dpm 644 %{SOURCE2} %{buildroot}%{_sysusersdir}/dnsmasq.conf %{_mandir}/man1/dhcp_* %changelog +* Wed Feb 28 2024 Tomas Korbar - 2.79-31.2 +- Fix CVE 2023-50387 and CVE 2023-50868 +- Resolves: RHEL-25628 +- Resolves: RHEL-25666 + +* Wed Nov 01 2023 Petr Menšík - 2.79-31.1 +- Do not crash on invalid domain in --synth-domain option (RHEL-22741) + * Wed Jun 14 2023 Petr Menšík - 2.79-31 - Do not create and search --local and --address=/x/# domains (#2233542)