Compare commits

..

No commits in common. "c8" and "c8-beta" have entirely different histories.
c8 ... c8-beta

9 changed files with 1 additions and 3213 deletions

View File

@ -1,432 +0,0 @@
commit 244dba98c6d93c84a362177bb4ef629b6b3345d0
Author: Tomas Korbar <tkorbar@redhat.com>
Date: Tue Aug 19 11:03:38 2025 +0200
Backport of upstream commits
45d8a2435e8200e892b82b6a04c7ddfb07a4165a
eb1fe15ca80b6bc43cd6bfdf309ec6c590aff811
diff --git a/src/cache.c b/src/cache.c
index 8b1b560..612ad22 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -77,17 +77,20 @@ static void cache_link(struct crec *crecp);
static void rehash(int size);
static void cache_hash(struct crec *crecp);
-static unsigned int next_uid(void)
+void next_uid(struct crec *crecp)
{
static unsigned int uid = 0;
- uid++;
-
- /* uid == 0 used to indicate CNAME to interface name. */
- if (uid == SRC_INTERFACE)
- uid++;
+ if (crecp->uid == UID_NONE)
+ {
+ uid++;
- return uid;
+ /* uid == 0 used to indicate CNAME to interface name. */
+ if (uid == UID_NONE)
+ uid++;
+
+ crecp->uid = uid;
+ }
}
void cache_init(void)
@@ -105,7 +108,7 @@ void cache_init(void)
{
cache_link(crecp);
crecp->flags = 0;
- crecp->uid = next_uid();
+ crecp->uid = UID_NONE;
}
}
@@ -204,7 +207,7 @@ static void cache_free(struct crec *crecp)
{
crecp->flags &= ~F_FORWARD;
crecp->flags &= ~F_REVERSE;
- crecp->uid = next_uid(); /* invalidate CNAMES pointing to this. */
+ crecp->uid = UID_NONE; /* invalidate CNAMES pointing to this. */
if (cache_tail)
cache_tail->next = crecp;
@@ -322,7 +325,8 @@ static int is_expired(time_t now, struct crec *crecp)
return 1;
}
-static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t now, unsigned short flags)
+static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t now, unsigned short flags,
+ struct crec **target_crec, unsigned int *target_uid)
{
/* Scan and remove old entries.
If (flags & F_FORWARD) then remove any forward entries for name and any expired
@@ -335,7 +339,10 @@ static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t no
to a cache entry if the name exists in the cache as a HOSTS or DHCP entry (these are never deleted)
We take advantage of the fact that hash chains have stuff in the order <reverse>,<other>,<immortal>
- so that when we hit an entry which isn't reverse and is immortal, we're done. */
+ so that when we hit an entry which isn't reverse and is immortal, we're done.
+
+ If we free a crec which is a CNAME target, return the entry and uid in target_crec and target_uid.
+ This entry will get re-used with the same name, to preserve CNAMEs. */
struct crec *crecp, **up;
@@ -343,17 +350,6 @@ static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t no
{
for (up = hash_bucket(name), crecp = *up; crecp; crecp = crecp->hash_next)
{
- if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp))
- {
- *up = crecp->hash_next;
- if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
- {
- cache_unlink(crecp);
- cache_free(crecp);
- }
- continue;
- }
-
if ((crecp->flags & F_FORWARD) && hostname_isequal(cache_get_name(crecp), name))
{
/* Don't delete DNSSEC in favour of a CNAME, they can co-exist */
@@ -363,6 +359,16 @@ static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t no
if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
return crecp;
*up = crecp->hash_next;
+ /* If this record is for the name we're inserting and is the target
+ of a CNAME record. Make the new record for the same name, in the same
+ crec, with the same uid to avoid breaking the existing CNAME. */
+ if (crecp->uid != UID_NONE)
+ {
+ if (target_crec)
+ *target_crec = crecp;
+ if (target_uid)
+ *target_uid = crecp->uid;
+ }
cache_unlink(crecp);
cache_free(crecp);
continue;
@@ -381,6 +387,18 @@ static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t no
}
#endif
}
+
+ if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp))
+ {
+ *up = crecp->hash_next;
+ if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
+ {
+ cache_unlink(crecp);
+ cache_free(crecp);
+ }
+ continue;
+ }
+
up = &crecp->hash_next;
}
}
@@ -447,11 +465,12 @@ void cache_start_insert(void)
struct crec *cache_insert(char *name, struct all_addr *addr,
time_t now, unsigned long ttl, unsigned short flags)
{
- struct crec *new;
+ struct crec *new, *target_crec = NULL;
union bigname *big_name = NULL;
int freed_all = flags & F_REVERSE;
int free_avail = 0;
-
+ unsigned int target_uid;
+
/* Don't log DNSSEC records here, done elsewhere */
if (flags & (F_IPV4 | F_IPV6 | F_CNAME))
{
@@ -469,7 +488,7 @@ struct crec *cache_insert(char *name, struct all_addr *addr,
/* First remove any expired entries and entries for the name/address we
are currently inserting. */
- if ((new = cache_scan_free(name, addr, now, flags)))
+ if ((new = cache_scan_free(name, addr, now, flags, &target_crec, &target_uid)))
{
/* We're trying to insert a record over one from
/etc/hosts or DHCP, or other config. If the
@@ -493,81 +512,89 @@ struct crec *cache_insert(char *name, struct all_addr *addr,
}
/* Now get a cache entry from the end of the LRU list */
- while (1) {
- if (!(new = cache_tail)) /* no entries left - cache is too small, bail */
- {
- insert_error = 1;
- return NULL;
- }
-
- /* End of LRU list is still in use: if we didn't scan all the hash
- chains for expired entries do that now. If we already tried that
- then it's time to start spilling things. */
-
- if (new->flags & (F_FORWARD | F_REVERSE))
- {
- /* If free_avail set, we believe that an entry has been freed.
- Bugs have been known to make this not true, resulting in
- a tight loop here. If that happens, abandon the
- insert. Once in this state, all inserts will probably fail. */
- if (free_avail)
- {
- static int warned = 0;
- if (!warned)
- {
- my_syslog(LOG_ERR, _("Internal error in cache."));
- warned = 1;
- }
- insert_error = 1;
- return NULL;
- }
-
- if (freed_all)
- {
- struct all_addr free_addr = new->addr.addr;;
+ if (!target_crec)
+ while (1) {
+ if (!(new = cache_tail)) /* no entries left - cache is too small, bail */
+ {
+ insert_error = 1;
+ return NULL;
+ }
+
+ /* Free entry at end of LRU list, use it. */
+ if (!(new->flags & (F_FORWARD | F_REVERSE)))
+ break;
+ /* End of LRU list is still in use: if we didn't scan all the hash
+ chains for expired entries do that now. If we already tried that
+ then it's time to start spilling things. */
+
+ /* If free_avail set, we believe that an entry has been freed.
+ Bugs have been known to make this not true, resulting in
+ a tight loop here. If that happens, abandon the
+ insert. Once in this state, all inserts will probably fail. */
+ if (free_avail)
+ {
+ static int warned = 0;
+ if (!warned)
+ {
+ my_syslog(LOG_ERR, _("Internal error in cache."));
+ warned = 1;
+ }
+ insert_error = 1;
+ return NULL;
+ }
+
+ if (freed_all)
+ {
+ struct all_addr free_addr = new->addr.addr;;
+
#ifdef HAVE_DNSSEC
- /* For DNSSEC records, addr holds class. */
- if (new->flags & (F_DS | F_DNSKEY))
- free_addr.addr.dnssec.class = new->uid;
+ /* For DNSSEC records, addr holds class. */
+ if (new->flags & (F_DS | F_DNSKEY))
+ free_addr.addr.dnssec.class = new->uid;
#endif
-
- free_avail = 1; /* Must be free space now. */
- cache_scan_free(cache_get_name(new), &free_addr, now, new->flags);
- cache_live_freed++;
- }
- else
- {
- cache_scan_free(NULL, NULL, now, 0);
- freed_all = 1;
- }
- continue;
- }
-
- /* Check if we need to and can allocate extra memory for a long name.
- If that fails, give up now, always succeed for DNSSEC records. */
- if (name && (strlen(name) > SMALLDNAME-1))
- {
- if (big_free)
- {
- big_name = big_free;
- big_free = big_free->next;
- }
- else if ((bignames_left == 0 && !(flags & (F_DS | F_DNSKEY))) ||
- !(big_name = (union bigname *)whine_malloc(sizeof(union bigname))))
- {
- insert_error = 1;
- return NULL;
- }
- else if (bignames_left != 0)
- bignames_left--;
-
- }
+
+ free_avail = 1; /* Must be free space now. */
+ cache_scan_free(cache_get_name(new), &free_addr, now, new->flags, NULL, NULL);
+ cache_live_freed++;
+ }
+ else
+ {
+ cache_scan_free(NULL, NULL, now, 0, NULL, NULL);
+ freed_all = 1;
+ }
+ }
+
+ /* Check if we need to and can allocate extra memory for a long name.
+ If that fails, give up now, always succeed for DNSSEC records. */
+ if (name && (strlen(name) > SMALLDNAME-1))
+ {
+ if (big_free)
+ {
+ big_name = big_free;
+ big_free = big_free->next;
+ }
+ else if ((bignames_left == 0 && !(flags & (F_DS | F_DNSKEY))) ||
+ !(big_name = (union bigname *)whine_malloc(sizeof(union bigname))))
+ {
+ insert_error = 1;
+ return NULL;
+ }
+ else if (bignames_left != 0)
+ bignames_left--;
+
+ }
- /* Got the rest: finally grab entry. */
- cache_unlink(new);
- break;
- }
+ /* If we freed a cache entry for our name which was a CNAME target, use that.
+ and preserve the uid, so that existing CNAMES are not broken. */
+ if (target_crec)
+ {
+ new = target_crec;
+ new->uid = target_uid;
+ }
+
+ /* Got the rest: finally grab entry. */
+ cache_unlink(new);
new->flags = flags;
if (big_name)
@@ -787,8 +814,9 @@ static void add_hosts_cname(struct crec *target)
crec->ttd = a->ttl;
crec->name.namep = a->alias;
crec->addr.cname.target.cache = target;
+ next_uid(target);
crec->addr.cname.uid = target->uid;
- crec->uid = next_uid();
+ crec->uid = UID_NONE;
cache_hash(crec);
add_hosts_cname(crec); /* handle chains */
}
@@ -1071,7 +1099,7 @@ void cache_reload(void)
cache->name.namep = a->alias;
cache->addr.cname.target.int_name = intr;
cache->addr.cname.uid = SRC_INTERFACE;
- cache->uid = next_uid();
+ cache->uid = UID_NONE;
cache_hash(cache);
add_hosts_cname(cache); /* handle chains */
}
@@ -1201,8 +1229,9 @@ static void add_dhcp_cname(struct crec *target, time_t ttd)
aliasc->ttd = ttd;
aliasc->name.namep = a->alias;
aliasc->addr.cname.target.cache = target;
+ next_uid(target);
aliasc->addr.cname.uid = target->uid;
- aliasc->uid = next_uid();
+ aliasc->uid = UID_NONE;
cache_hash(aliasc);
add_dhcp_cname(aliasc, ttd);
}
@@ -1243,7 +1272,7 @@ void cache_add_dhcp_entry(char *host_name, int prot,
}
else if (!(crec->flags & F_DHCP))
{
- cache_scan_free(host_name, NULL, 0, crec->flags & (flags | F_CNAME | F_FORWARD));
+ cache_scan_free(host_name, NULL, 0, crec->flags & (flags | F_CNAME | F_FORWARD), NULL, NULL);
/* scan_free deletes all addresses associated with name */
break;
}
@@ -1270,7 +1299,7 @@ void cache_add_dhcp_entry(char *host_name, int prot,
if (crec->flags & F_NEG)
{
flags |= F_REVERSE;
- cache_scan_free(NULL, (struct all_addr *)host_address, 0, flags);
+ cache_scan_free(NULL, (struct all_addr *)host_address, 0, flags, NULL, NULL);
}
}
else
@@ -1290,7 +1319,7 @@ void cache_add_dhcp_entry(char *host_name, int prot,
crec->ttd = ttd;
crec->addr.addr = *host_address;
crec->name.namep = host_name;
- crec->uid = next_uid();
+ crec->uid = UID_NONE;
cache_hash(crec);
add_dhcp_cname(crec, ttd);
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index cf1c9c4..2d83a48 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -471,8 +471,12 @@ struct crec {
#define F_NOEXTRA (1u<<27)
#define F_SERVFAIL (1u<<28)
+#define UID_NONE 0
/* Values of uid in crecs with F_CONFIG bit set. */
-#define SRC_INTERFACE 0
+/* cname to uid SRC_INTERFACE are to interface names,
+ so use UID_NONE for that to eliminate clashes with
+ any other uid */
+#define SRC_INTERFACE UID_NONE
#define SRC_CONFIG 1
#define SRC_HOSTS 2
#define SRC_AH 3
@@ -1147,6 +1151,7 @@ extern struct daemon {
/* cache.c */
void cache_init(void);
+void next_uid(struct crec *crecp);
void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg);
char *record_source(unsigned int index);
char *querystr(char *desc, unsigned short type);
diff --git a/src/rfc1035.c b/src/rfc1035.c
index 48804b8..021661e 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -739,6 +739,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
newc->addr.cname.uid = 1;
if (cpp)
{
+ next_uid(newc);
cpp->addr.cname.target.cache = newc;
cpp->addr.cname.uid = newc->uid;
}
@@ -795,6 +796,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
newc = cache_insert(name, &addr, now, attl, flags | F_FORWARD | secflag);
if (newc && cpp)
{
+ next_uid(newc);
cpp->addr.cname.target.cache = newc;
cpp->addr.cname.uid = newc->uid;
}
@@ -821,6 +823,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0));
if (newc && cpp)
{
+ next_uid(newc);
cpp->addr.cname.target.cache = newc;
cpp->addr.cname.uid = newc->uid;
}

View File

@ -1,207 +0,0 @@
From b133ed74c5eb836f2a5add1f4de1c662b67f841a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
Date: Mon, 11 Aug 2025 17:29:52 +0200
Subject: [PATCH] Fix problem with DNS retries in 2.83/2.84.
Combination of upstream commits:
- 141a26f979b4bc959d8e866a295e24f8cf456920
- 305cb79c5754d5554729b18a2c06fe7ce699687a
And following commit
Subtly change behaviour on repeated DNS query.
This changes the behaviour introduced in
141a26f979b4bc959d8e866a295e24f8cf456920
We re-introduce the distinction between a query
which is retried from the same source, and one which is
repeated from different sources.
In the later case, we still forward the query, to avoid
problems when the reply to the first query is lost
(see f8cf456920) but we suppress the behaviour
that's used on a retry, when the query is sent to
all available servers in parallel.
Retry -> all servers.
Repeat -> next server.
This avoids a significant increase in upstream traffic on
busy instances which see lots of queries for common names.
It does mean the clients which repeat queries from new source ports,
rather than retrying them from the same source port, will see
different behaviour, but it in fact restores the pre-2.83 behaviour,
so it's not expected to be a practical problem.
(cherry picked from commit ea6b0b2665ed95baa41fe813547103f7f1ccb953)
---
src/forward.c | 114 ++++++++++++++++++++++----------------------------
1 file changed, 51 insertions(+), 63 deletions(-)
diff --git a/src/forward.c b/src/forward.c
index 64124b1..d27f230 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -17,9 +17,6 @@
#include "dnsmasq.h"
static struct frec *lookup_frec(unsigned short id, int fd, void *hash);
-static struct frec *lookup_frec_by_sender(unsigned short id,
- union mysockaddr *addr,
- void *hash);
static struct frec *lookup_frec_by_query(void *hash, unsigned int flags);
static unsigned short get_id(void);
@@ -257,6 +254,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
#endif
unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
unsigned char *oph = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL);
+ int old_src = 0;
(void)do_bit;
@@ -270,9 +268,53 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
if (do_bit)
fwd_flags |= FREC_DO_QUESTION;
#endif
-
- /* may be no servers available. */
- if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))
+
+ /* Check for retry on existing query from same source */
+ if (forward)
+ old_src = 1;
+ else if ((forward = lookup_frec_by_query(hash, fwd_flags)))
+ {
+ struct frec_src *src;
+
+ for (src = &forward->frec_src; src; src = src->next)
+ if (src->orig_id == ntohs(header->id) &&
+ sockaddr_isequal(&src->source, udpaddr))
+ break;
+
+ if (src)
+ old_src = 1;
+ else
+ {
+ /* Existing query, but from new source, just add this
+ client to the list that will get the reply.*/
+
+ /* Note whine_malloc() zeros memory. */
+ if (!daemon->free_frec_src &&
+ daemon->frec_src_count < daemon->ftabsize &&
+ (daemon->free_frec_src = whine_malloc(sizeof(struct frec_src))))
+ {
+ daemon->frec_src_count++;
+ daemon->free_frec_src->next = NULL;
+ }
+
+ /* If we've been spammed with many duplicates, just drop the query. */
+ if (daemon->free_frec_src)
+ {
+ src = daemon->free_frec_src;
+ daemon->free_frec_src = src->next;
+ src->next = forward->frec_src.next;
+ forward->frec_src.next = src;
+ src->orig_id = ntohs(header->id);
+ src->source = *udpaddr;
+ src->dest = *dst_addr;
+ src->log_id = daemon->log_id;
+ src->iface = dst_iface;
+ src->fd = udpfd;
+ }
+ }
+ }
+
+ if (forward)
{
/* If we didn't get an answer advertising a maximal packet in EDNS,
fall back to 1280, which should work everywhere on IPv6.
@@ -317,10 +359,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
}
#endif
- /* retry on existing query, send to all available servers */
+ /* retry on existing query, from original source. Send to all available servers */
domain = forward->sentto->domain;
forward->sentto->failed_queries++;
- if (!option_bool(OPT_ORDER))
+ if (!option_bool(OPT_ORDER) && old_src)
{
sd = forward->sentto->serv_domain;
forward->forwardall = 1;
@@ -338,42 +380,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
}
else
{
- /* Query from new source, but the same query may be in progress
- from another source. If so, just add this client to the
- list that will get the reply.
-
- Note that is the EDNS client subnet option is in use, we can't do this,
- as the clients (and therefore query EDNS options) will be different
- for each query. The EDNS subnet code has checks to avoid
- attacks in this case. */
- if (!option_bool(OPT_CLIENT_SUBNET) && (forward = lookup_frec_by_query(hash, fwd_flags)))
- {
- /* Note whine_malloc() zeros memory. */
- if (!daemon->free_frec_src &&
- daemon->frec_src_count < daemon->ftabsize &&
- (daemon->free_frec_src = whine_malloc(sizeof(struct frec_src))))
- {
- daemon->frec_src_count++;
- daemon->free_frec_src->next = NULL;
- }
-
- /* If we've been spammed with many duplicates, just drop the query. */
- if (daemon->free_frec_src)
- {
- struct frec_src *new = daemon->free_frec_src;
- daemon->free_frec_src = new->next;
- new->next = forward->frec_src.next;
- forward->frec_src.next = new;
- new->orig_id = ntohs(header->id);
- new->source = *udpaddr;
- new->dest = *dst_addr;
- new->log_id = daemon->log_id;
- new->iface = dst_iface;
- new->fd = udpfd;
- }
-
- return 1;
- }
+ /* new query */
if (gotname)
flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind, &sd);
@@ -2424,25 +2431,6 @@ static struct frec *lookup_frec(unsigned short id, int fd, void *hash)
return NULL;
}
-static struct frec *lookup_frec_by_sender(unsigned short id,
- union mysockaddr *addr,
- void *hash)
-{
- struct frec *f;
- struct frec_src *src;
-
- for (f = daemon->frec_list; f; f = f->next)
- if (f->sentto &&
- !(f->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) &&
- memcmp(hash, f->hash, HASH_SIZE) == 0)
- for (src = &f->frec_src; src; src = src->next)
- if (src->orig_id == id &&
- sockaddr_isequal(&src->source, addr))
- return f;
-
- return NULL;
-}
-
static struct frec *lookup_frec_by_query(void *hash, unsigned int flags)
{
struct frec *f;
--
2.50.1

File diff suppressed because it is too large Load Diff

View File

@ -1,36 +0,0 @@
From b8544d5802e56186eb144fbcdd18070b01dc9ab0 Mon Sep 17 00:00:00 2001
From: Simon Kelley <simon@thekelleys.org.uk>
Date: Fri, 10 Apr 2026 16:29:31 +0100
Subject: [PATCH 1/5] Fix buffer overflow in struct bigname. CVE-2026-2291
All buffers capable of holding a domain name should be
at least MAXDNAME*2 + 1 bytes long, where MAXDNAME is the maximum
size of a domain name. The accounts for the trailing zero and the
fact that some characters are escaped in the internal representation
of a domain name in dnsmasq.
The declaration of struct bigname get this wrong, with the effect
that a remote attacker capable of asking DNS queries or answering DNS
queries can cause a large OOB write in the heap.
This was first spotted by Andrew S. Fasano.
---
src/dnsmasq.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index e455c3f..be8cf2a 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -467,7 +467,7 @@ struct interface_name {
};
union bigname {
- char name[MAXDNAME];
+ char name[(2*MAXDNAME) + 1];
union bigname *next; /* freelist */
};
--
2.54.0

View File

@ -1,70 +0,0 @@
From 09fe631edd6d95630efc11bec8c5017705e68a10 Mon Sep 17 00:00:00 2001
From: Simon Kelley <simon@thekelleys.org.uk>
Date: Fri, 10 Apr 2026 22:16:45 +0100
Subject: [PATCH 2/5] Fix NSEC bitmap parsing infinite loop. CVE-2026-4890
Report from Royce M <royce@xchglabs.com>.
Location: dnssec.c:1290-1306, dnssec.c:1450-1463
The bitmap window iteration advances by p[1] instead of p[1]+2 (missing the 2-byte window header). With bitmap_length=0, both rdlen and p are
unchanged, causing an infinite loop and dnsmasq stops responding to all queries.
The same code accesses p[2] after only checking rdlen >= 2 without verifying p[1] >= 1, causing OOB reads at 6 locations.
Both bugs are reachable before RRSIG validation (confirmed by the source comment at line 2125), so no valid DNSSEC signatures are needed.
---
src/dnssec.c | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/dnssec.c b/src/dnssec.c
index ed2f53f..68f1b5d 100644
--- a/src/dnssec.c
+++ b/src/dnssec.c
@@ -1270,10 +1270,10 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
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)
+ if (nons && rdlen >= 2 && p[0] == 0 && p[1] >= 1 && (p[2] & (0x80 >> T_NS)) != 0)
*nons = 0;
- if (rdlen >= 2 && p[0] == 0)
+ if (rdlen >= 2 && p[0] == 0 && p[1] >= 1)
{
/* A CNAME answer would also be valid, so if there's a CNAME is should
have been returned. */
@@ -1301,8 +1301,8 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
break; /* finished checking */
}
- rdlen -= p[1];
- p += p[1];
+ rdlen -= p[1] + 2;
+ p += p[1] + 2;
}
return 0;
@@ -1429,7 +1429,7 @@ 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 (rdlen >= 2 && p[0] == 0)
+ if (rdlen >= 2 && p[0] == 0 && p[1] >= 1)
{
/* If we can prove that there's no NS record, return that information. */
if (nons && (p[2] & (0x80 >> T_NS)) != 0)
@@ -1458,8 +1458,8 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
break; /* finished checking */
}
- rdlen -= p[1];
- p += p[1];
+ rdlen -= p[1] + 2;
+ p += p[1] + 2;
}
return 1;
--
2.54.0

View File

@ -1,39 +0,0 @@
From 2efe6d3acaf840fa06d58b6fad21ad73d0865716 Mon Sep 17 00:00:00 2001
From: Simon Kelley <simon@thekelleys.org.uk>
Date: Wed, 25 Mar 2026 23:04:08 +0000
Subject: [PATCH 3/5] Verify rdlen field in RRSIG packets. CVE-2026-4891
Bug report from Royce M <royce@xchglabs.com>
This avoids crafted packets which give a value for rdlen _less_
then the space taken up by the fixed data and the signer's name
and engender a negative calculated length for the signature.
---
src/dnssec.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/dnssec.c b/src/dnssec.c
index 68f1b5d..d32db5b 100644
--- a/src/dnssec.c
+++ b/src/dnssec.c
@@ -546,10 +546,14 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
*ttl_out = ttl;
}
-
+
+ /* Don't trust rdlen not to be too small and give us a negative sig_len
+ It has already been checked that it doesn't run us off the end
+ of the packet. */
+ if ((sig_len = rdlen - (p - psav)) <= 0)
+ return STAT_BOGUS;
+
sig = p;
- sig_len = rdlen - (p - psav);
-
nsigttl = htonl(orig_ttl);
hash->update(ctx, 18, psav);
--
2.54.0

View File

@ -1,36 +0,0 @@
From e0a5f7bef040d25631ffff9abaf8424091b768bc Mon Sep 17 00:00:00 2001
From: Simon Kelley <simon@thekelleys.org.uk>
Date: Wed, 25 Mar 2026 23:16:35 +0000
Subject: [PATCH 4/5] Fix buffer overflow in helper.c with large CLIDs.
CVE-2026-4892
Bug reported bt Royce M <royce@xchglabs.com>
Location: helper.c:265-270
DHCPv6 CLIDs can be up to 65535 bytes. When --dhcp-script is configured,
the helper hex-encodes raw CLID bytes via sprintf("%.2x") into daemon->packet (5131 bytes).
A 1000-byte CLID writes ~3000 bytes. The helper process retains root privileges.
Note: log6_packet() correctly caps CLID to 100 bytes for logging, but the helper code path was missed.
---
src/helper.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/helper.c b/src/helper.c
index b9da225..3a31e61 100644
--- a/src/helper.c
+++ b/src/helper.c
@@ -261,8 +261,8 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
data.hostname_len + data.ed_len + data.clid_len, 1))
continue;
- /* CLID into packet */
- for (p = daemon->packet, i = 0; i < data.clid_len; i++)
+ /* CLID into packet: limit to 100 bytes to avoid overflowing buffer. */
+ for (p = daemon->packet, i = 0; i < data.clid_len && i < 100; i++)
{
p += sprintf(p, "%.2x", buf[i]);
if (i != data.clid_len - 1)
--
2.54.0

View File

@ -1,33 +0,0 @@
From 4b8388d967e207b277c45e1fc0fb646767e5ca5d Mon Sep 17 00:00:00 2001
From: Simon Kelley <simon@thekelleys.org.uk>
Date: Wed, 25 Mar 2026 23:22:37 +0000
Subject: [PATCH] Fix broken client subnet validation. CVE-2026-4893
Bug report from Royce M <royce@xchglabs.com>
Location: forward.c:713, edns0.c:421
With --add-subnet enabled, process_reply() passes the OPT record
length (~23 bytes) instead of the packet length to check_source().
All internal bounds checks fail, and the function always returns 1.
ECS source validation per RFC 7871 Section 9.2 is completely bypassed.
---
src/forward.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/forward.c b/src/forward.c
index d27f230..156770a 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -629,7 +629,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign, NULL)))
{
- if (check_subnet && !check_source(header, plen, pheader, query_source))
+ if (check_subnet && !check_source(header, n, pheader, query_source))
{
my_syslog(LOG_WARNING, _("discarding DNS reply: subnet option mismatch"));
return 0;
--
2.54.0

View File

@ -13,7 +13,7 @@
Name: dnsmasq
Version: 2.79
Release: 36%{?extraversion:.%{extraversion}}%{?dist}
Release: 32%{?extraversion:.%{extraversion}}%{?dist}
Summary: A lightweight DHCP/caching DNS server
License: GPLv2 or GPLv3
@ -98,20 +98,6 @@ Patch43: dnsmasq-2.87-log-root-writeable.patch
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
# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=141a26f979b4bc959d8e866a295e24f8cf456920
# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=305cb79c5754d5554729b18a2c06fe7ce699687a
# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=ea6b0b2665ed95baa41fe813547103f7f1ccb953
Patch47: dnsmasq-2.85-forward-retries.patch
# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=45d8a2435e8200e892b82b6a04c7ddfb07a4165a
# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=eb1fe15ca80b6bc43cd6bfdf309ec6c590aff811
Patch48: dnsmasq-2.79-cname-collision.patch
Patch49: dnsmasq-2.93-CVE-2026-2291.patch
Patch50: dnsmasq-2.93-CVE-2026-4890.patch
Patch51: dnsmasq-2.93-CVE-2026-4891.patch
Patch52: dnsmasq-2.93-CVE-2026-4892.patch
Patch53: dnsmasq-2.93-CVE-2026-4893.patch
# This is workaround to nettle bug #1549190
# https://bugzilla.redhat.com/show_bug.cgi?id=1549190
@ -190,14 +176,6 @@ server's leases.
%patch43 -p1 -b .rh2156789
%patch44 -p1 -b .rh2209031
%patch45 -p1 -b .RHEL-15216
%patch46 -p1 -b .CVE-2023-50387-CVE-2023-50868
%patch47 -p1 -b .RHEL-6586
%patch48 -p1 -b .RHEL-61943
%patch49 -p1 -b .CVE-2026-2291
%patch50 -p1 -b .CVE-2026-4890
%patch51 -p1 -b .CVE-2026-4891
%patch52 -p1 -b .CVE-2026-4892
%patch53 -p1 -b .CVE-2026-4893
# 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
@ -297,25 +275,6 @@ install -Dpm 644 %{SOURCE2} %{buildroot}%{_sysusersdir}/dnsmasq.conf
%{_mandir}/man1/dhcp_*
%changelog
* Tue May 05 2026 Petr Menšík <pemensik@redhat.com> - 2.79-36
- Prevent overflow in extract_name function (CVE-2026-2291)
- Prevent DoS in DNSSEC validation (CVE-2026-4890)
- Prevent out-of-bounds read in DNSSEC validation (CVE-2026-4891)
- Prevent out-of-bounds write in DHCPv6 server (CVE-2026-4892)
- Prevent source check avoidance by RFC 7871 client-subnet (CVE-2026-4893)
* Mon Aug 18 2025 Tomas Korbar <tkorbar@redhat.com> - 2.79-35
- Fix dnsmasq caching of intertwined CNAMES
- Resolves: RHEL-61943
* Mon Aug 11 2025 Petr Menšík <pemensik@redhat.com> - 2.79-34
- Enable retries again (RHEL-6586)
* Mon Mar 18 2024 Tomas Korbar <tkorbar@redhat.com> - 2.79-33
- Fix CVE 2023-50387 and CVE 2023-50868
- Resolves: RHEL-25667
- Resolves: RHEL-25629
* Wed Nov 01 2023 Petr Menšík <pemensik@redhat.com> - 2.79-32
- Do not crash on invalid domain in --synth-domain option (RHEL-15216)