aboutsummaryrefslogtreecommitdiffstats
path: root/meta-networking/recipes-support/dnsmasq
diff options
context:
space:
mode:
Diffstat (limited to 'meta-networking/recipes-support/dnsmasq')
-rw-r--r--meta-networking/recipes-support/dnsmasq/dnsmasq/CVE-2021-3448.patch1040
-rw-r--r--meta-networking/recipes-support/dnsmasq/dnsmasq_2.81.bb1
2 files changed, 1041 insertions, 0 deletions
diff --git a/meta-networking/recipes-support/dnsmasq/dnsmasq/CVE-2021-3448.patch b/meta-networking/recipes-support/dnsmasq/dnsmasq/CVE-2021-3448.patch
new file mode 100644
index 000000000..360931a83
--- /dev/null
+++ b/meta-networking/recipes-support/dnsmasq/dnsmasq/CVE-2021-3448.patch
@@ -0,0 +1,1040 @@
+From 74d4fcd756a85bc1823232ea74334f7ccfb9d5d2 Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Mon, 15 Mar 2021 21:59:51 +0000
+Subject: [PATCH] Use random source ports where possible if source
+ addresses/interfaces in use.
+
+CVE-2021-3448 applies.
+
+It's possible to specify the source address or interface to be
+used when contacting upstream nameservers: server=8.8.8.8@1.2.3.4
+or server=8.8.8.8@1.2.3.4#66 or server=8.8.8.8@eth0, and all of
+these have, until now, used a single socket, bound to a fixed
+port. This was originally done to allow an error (non-existent
+interface, or non-local address) to be detected at start-up. This
+means that any upstream servers specified in such a way don't use
+random source ports, and are more susceptible to cache-poisoning
+attacks.
+
+We now use random ports where possible, even when the
+source is specified, so server=8.8.8.8@1.2.3.4 or
+server=8.8.8.8@eth0 will use random source
+ports. server=8.8.8.8@1.2.3.4#66 or any use of --query-port will
+use the explicitly configured port, and should only be done with
+understanding of the security implications.
+Note that this change changes non-existing interface, or non-local
+source address errors from fatal to run-time. The error will be
+logged and communiction with the server not possible.
+
+Upstream-Status: Backport
+CVE: CVE-2021-3448
+Signed-off-by: Armin Kuster <akuster@mvista.com>
+
+---
+ CHANGELOG | 22 +++
+ man/dnsmasq.8 | 4 +-
+ src/dnsmasq.c | 31 ++--
+ src/dnsmasq.h | 26 ++--
+ src/forward.c | 392 ++++++++++++++++++++++++++++++--------------------
+ src/loop.c | 20 +--
+ src/network.c | 110 +++++---------
+ src/option.c | 3 +-
+ src/tftp.c | 6 +-
+ src/util.c | 2 +-
+ 10 files changed, 344 insertions(+), 272 deletions(-)
+
+Index: dnsmasq-2.81/man/dnsmasq.8
+===================================================================
+--- dnsmasq-2.81.orig/man/dnsmasq.8
++++ dnsmasq-2.81/man/dnsmasq.8
+@@ -489,7 +489,7 @@ source address specified but the port ma
+ part of the source address. Forcing queries to an interface is not
+ implemented on all platforms supported by dnsmasq.
+ .TP
+-.B --rev-server=<ip-address>/<prefix-len>[,<ipaddr>][#<port>][@<source-ip>|<interface>[#<port>]]
++.B --rev-server=<ip-address>/<prefix-len>[,<ipaddr>][#<port>][@<interface>][@<source-ip>[#<port>]]
+ This is functionally the same as
+ .B --server,
+ but provides some syntactic sugar to make specifying address-to-name queries easier. For example
+Index: dnsmasq-2.81/src/dnsmasq.c
+===================================================================
+--- dnsmasq-2.81.orig/src/dnsmasq.c
++++ dnsmasq-2.81/src/dnsmasq.c
+@@ -1668,6 +1668,7 @@ static int set_dns_listeners(time_t now)
+ {
+ struct serverfd *serverfdp;
+ struct listener *listener;
++ struct randfd_list *rfl;
+ int wait = 0, i;
+
+ #ifdef HAVE_TFTP
+@@ -1688,11 +1689,14 @@ static int set_dns_listeners(time_t now)
+ for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
+ poll_listen(serverfdp->fd, POLLIN);
+
+- if (daemon->port != 0 && !daemon->osport)
+- for (i = 0; i < RANDOM_SOCKS; i++)
+- if (daemon->randomsocks[i].refcount != 0)
+- poll_listen(daemon->randomsocks[i].fd, POLLIN);
+-
++ for (i = 0; i < RANDOM_SOCKS; i++)
++ if (daemon->randomsocks[i].refcount != 0)
++ poll_listen(daemon->randomsocks[i].fd, POLLIN);
++
++ /* Check overflow random sockets too. */
++ for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next)
++ poll_listen(rfl->rfd->fd, POLLIN);
++
+ for (listener = daemon->listeners; listener; listener = listener->next)
+ {
+ /* only listen for queries if we have resources */
+@@ -1729,18 +1733,23 @@ static void check_dns_listeners(time_t n
+ {
+ struct serverfd *serverfdp;
+ struct listener *listener;
++ struct randfd_list *rfl;
+ int i;
+ int pipefd[2];
+
+ for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
+ if (poll_check(serverfdp->fd, POLLIN))
+- reply_query(serverfdp->fd, serverfdp->source_addr.sa.sa_family, now);
++ reply_query(serverfdp->fd, now);
+
+- if (daemon->port != 0 && !daemon->osport)
+- for (i = 0; i < RANDOM_SOCKS; i++)
+- if (daemon->randomsocks[i].refcount != 0 &&
+- poll_check(daemon->randomsocks[i].fd, POLLIN))
+- reply_query(daemon->randomsocks[i].fd, daemon->randomsocks[i].family, now);
++ for (i = 0; i < RANDOM_SOCKS; i++)
++ if (daemon->randomsocks[i].refcount != 0 &&
++ poll_check(daemon->randomsocks[i].fd, POLLIN))
++ reply_query(daemon->randomsocks[i].fd, now);
++
++ /* Check overflow random sockets too. */
++ for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next)
++ if (poll_check(rfl->rfd->fd, POLLIN))
++ reply_query(rfl->rfd->fd, now);
+
+ /* Races. The child process can die before we read all of the data from the
+ pipe, or vice versa. Therefore send tcp_pids to zero when we wait() the
+Index: dnsmasq-2.81/src/dnsmasq.h
+===================================================================
+--- dnsmasq-2.81.orig/src/dnsmasq.h
++++ dnsmasq-2.81/src/dnsmasq.h
+@@ -542,13 +542,20 @@ struct serverfd {
+ };
+
+ struct randfd {
++ struct server *serv;
+ int fd;
+- unsigned short refcount, family;
++ unsigned short refcount; /* refcount == 0xffff means overflow record. */
+ };
+-
++
++struct randfd_list {
++ struct randfd *rfd;
++ struct randfd_list *next;
++};
++
+ struct server {
+ union mysockaddr addr, source_addr;
+ char interface[IF_NAMESIZE+1];
++ unsigned int ifindex; /* corresponding to interface, above */
+ struct serverfd *sfd;
+ char *domain; /* set if this server only handles a domain. */
+ int flags, tcpfd, edns_pktsz;
+@@ -669,8 +676,7 @@ struct frec {
+ struct frec_src *next;
+ } frec_src;
+ struct server *sentto; /* NULL means free */
+- struct randfd *rfd4;
+- struct randfd *rfd6;
++ struct randfd_list *rfds;
+ unsigned short new_id;
+ int fd, forwardall, flags;
+ time_t time;
+@@ -1100,11 +1106,12 @@ extern struct daemon {
+ int forwardcount;
+ struct server *srv_save; /* Used for resend on DoD */
+ size_t packet_len; /* " " */
+- struct randfd *rfd_save; /* " " */
++ int fd_save; /* " " */
+ pid_t tcp_pids[MAX_PROCS];
+ int tcp_pipes[MAX_PROCS];
+ int pipe_to_parent;
+ struct randfd randomsocks[RANDOM_SOCKS];
++ struct randfd_list *rfl_spare, *rfl_poll;
+ int v6pktinfo;
+ struct addrlist *interface_addrs; /* list of all addresses/prefix lengths associated with all local interfaces */
+ int log_id, log_display_id; /* ids of transactions for logging */
+@@ -1275,7 +1282,7 @@ void safe_strncpy(char *dest, const char
+ void safe_pipe(int *fd, int read_noblock);
+ void *whine_malloc(size_t size);
+ int sa_len(union mysockaddr *addr);
+-int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2);
++int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2);
+ int hostname_isequal(const char *a, const char *b);
+ int hostname_issubdomain(char *a, char *b);
+ time_t dnsmasq_time(void);
+@@ -1326,7 +1333,7 @@ char *parse_server(char *arg, union myso
+ int option_read_dynfile(char *file, int flags);
+
+ /* forward.c */
+-void reply_query(int fd, int family, time_t now);
++void reply_query(int fd, time_t now);
+ void receive_query(struct listener *listen, time_t now);
+ unsigned char *tcp_request(int confd, time_t now,
+ union mysockaddr *local_addr, struct in_addr netmask, int auth_dns);
+@@ -1336,13 +1343,12 @@ int send_from(int fd, int nowild, char *
+ union mysockaddr *to, union all_addr *source,
+ unsigned int iface);
+ void resend_query(void);
+-struct randfd *allocate_rfd(int family);
+-void free_rfd(struct randfd *rfd);
++int allocate_rfd(struct randfd_list **fdlp, struct server *serv);
++void free_rfds(struct randfd_list **fdlp);
+
+ /* network.c */
+ int indextoname(int fd, int index, char *name);
+ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp);
+-int random_sock(int family);
+ void pre_allocate_sfds(void);
+ int reload_servers(char *fname);
+ void mark_servers(int flag);
+Index: dnsmasq-2.81/src/forward.c
+===================================================================
+--- dnsmasq-2.81.orig/src/forward.c
++++ dnsmasq-2.81/src/forward.c
+@@ -16,7 +16,7 @@
+
+ #include "dnsmasq.h"
+
+-static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash);
++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);
+@@ -307,26 +307,18 @@ static int forward_query(int udpfd, unio
+ if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign)
+ PUTSHORT(SAFE_PKTSZ, pheader);
+
+- if (forward->sentto->addr.sa.sa_family == AF_INET)
+- log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (union all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec");
+- else
+- log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (union all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec");
+-
+-
+- if (forward->sentto->sfd)
+- fd = forward->sentto->sfd->fd;
+- else
++ if ((fd = allocate_rfd(&forward->rfds, forward->sentto)) != -1)
+ {
+- if (forward->sentto->addr.sa.sa_family == AF_INET6)
+- fd = forward->rfd6->fd;
++ if (forward->sentto->addr.sa.sa_family == AF_INET)
++ log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (union all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec");
+ else
+- fd = forward->rfd4->fd;
++ log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (union all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec");
++
++ while (retry_send(sendto(fd, (char *)header, plen, 0,
++ &forward->sentto->addr.sa,
++ sa_len(&forward->sentto->addr))));
+ }
+
+- while (retry_send(sendto(fd, (char *)header, plen, 0,
+- &forward->sentto->addr.sa,
+- sa_len(&forward->sentto->addr))));
+-
+ return 1;
+ }
+ #endif
+@@ -501,49 +493,28 @@ static int forward_query(int udpfd, unio
+
+ while (1)
+ {
++ int fd;
++
+ /* only send to servers dealing with our domain.
+ domain may be NULL, in which case server->domain
+ must be NULL also. */
+
+ if (type == (start->flags & SERV_TYPE) &&
+ (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) &&
+- !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)))
++ !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)) &&
++ ((fd = allocate_rfd(&forward->rfds, start)) != -1))
+ {
+- int fd;
+-
+- /* find server socket to use, may need to get random one. */
+- if (start->sfd)
+- fd = start->sfd->fd;
+- else
+- {
+- if (start->addr.sa.sa_family == AF_INET6)
+- {
+- if (!forward->rfd6 &&
+- !(forward->rfd6 = allocate_rfd(AF_INET6)))
+- break;
+- daemon->rfd_save = forward->rfd6;
+- fd = forward->rfd6->fd;
+- }
+- else
+- {
+- if (!forward->rfd4 &&
+- !(forward->rfd4 = allocate_rfd(AF_INET)))
+- break;
+- daemon->rfd_save = forward->rfd4;
+- fd = forward->rfd4->fd;
+- }
+
+ #ifdef HAVE_CONNTRACK
+- /* Copy connection mark of incoming query to outgoing connection. */
+- if (option_bool(OPT_CONNTRACK))
+- {
+- unsigned int mark;
+- if (get_incoming_mark(&forward->source, &forward->dest, 0, &mark))
+- setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
+- }
+-#endif
++ /* Copy connection mark of incoming query to outgoing connection. */
++ if (option_bool(OPT_CONNTRACK))
++ {
++ unsigned int mark;
++ if (get_incoming_mark(&forward->frec_src.source, &forward->frec_src.dest, 0, &mark))
++ setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
+ }
+-
++#endif
++
+ #ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER))
+ {
+@@ -574,6 +545,7 @@ static int forward_query(int udpfd, unio
+ /* Keep info in case we want to re-send this packet */
+ daemon->srv_save = start;
+ daemon->packet_len = plen;
++ daemon->fd_save = fd;
+
+ if (!gotname)
+ strcpy(daemon->namebuff, "query");
+@@ -590,7 +562,7 @@ static int forward_query(int udpfd, unio
+ break;
+ forward->forwardall++;
+ }
+- }
++ }
+
+ if (!(start = start->next))
+ start = daemon->servers;
+@@ -805,7 +777,7 @@ static size_t process_reply(struct dns_h
+ }
+
+ /* sets new last_server */
+-void reply_query(int fd, int family, time_t now)
++void reply_query(int fd, time_t now)
+ {
+ /* packet from peer server, extract data for cache, and send to
+ original requester */
+@@ -820,9 +792,9 @@ void reply_query(int fd, int family, tim
+
+ /* packet buffer overwritten */
+ daemon->srv_save = NULL;
+-
++
+ /* Determine the address of the server replying so that we can mark that as good */
+- if ((serveraddr.sa.sa_family = family) == AF_INET6)
++ if (serveraddr.sa.sa_family == AF_INET6)
+ serveraddr.in6.sin6_flowinfo = 0;
+
+ header = (struct dns_header *)daemon->packet;
+@@ -845,7 +817,7 @@ void reply_query(int fd, int family, tim
+
+ hash = hash_questions(header, n, daemon->namebuff);
+
+- if (!(forward = lookup_frec(ntohs(header->id), fd, family, hash)))
++ if (!(forward = lookup_frec(ntohs(header->id), fd, hash)))
+ return;
+
+ #ifdef HAVE_DUMPFILE
+@@ -900,25 +872,8 @@ void reply_query(int fd, int family, tim
+ }
+
+
+- if (start->sfd)
+- fd = start->sfd->fd;
+- else
+- {
+- if (start->addr.sa.sa_family == AF_INET6)
+- {
+- /* may have changed family */
+- if (!forward->rfd6)
+- forward->rfd6 = allocate_rfd(AF_INET6);
+- fd = forward->rfd6->fd;
+- }
+- else
+- {
+- /* may have changed family */
+- if (!forward->rfd4)
+- forward->rfd4 = allocate_rfd(AF_INET);
+- fd = forward->rfd4->fd;
+- }
+- }
++ if ((fd = allocate_rfd(&forward->rfds, start)) == -1)
++ return;
+
+ #ifdef HAVE_DUMPFILE
+ dump_packet(DUMP_SEC_QUERY, (void *)header, (size_t)plen, NULL, &start->addr);
+@@ -1126,8 +1081,7 @@ void reply_query(int fd, int family, tim
+ }
+
+ new->sentto = server;
+- new->rfd4 = NULL;
+- new->rfd6 = NULL;
++ new->rfds = NULL;
+ new->frec_src.next = NULL;
+ new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA);
+ new->forwardall = 0;
+@@ -1166,24 +1120,7 @@ void reply_query(int fd, int family, tim
+ /* Don't resend this. */
+ daemon->srv_save = NULL;
+
+- if (server->sfd)
+- fd = server->sfd->fd;
+- else
+- {
+- fd = -1;
+- if (server->addr.sa.sa_family == AF_INET6)
+- {
+- if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6)))
+- fd = new->rfd6->fd;
+- }
+- else
+- {
+- if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET)))
+- fd = new->rfd4->fd;
+- }
+- }
+-
+- if (fd != -1)
++ if ((fd = allocate_rfd(&new->rfds, server)) != -1)
+ {
+ #ifdef HAVE_CONNTRACK
+ /* Copy connection mark of incoming query to outgoing connection. */
+@@ -1344,7 +1281,7 @@ void receive_query(struct listener *list
+
+ /* packet buffer overwritten */
+ daemon->srv_save = NULL;
+-
++
+ dst_addr_4.s_addr = dst_addr.addr4.s_addr = 0;
+ netmask.s_addr = 0;
+
+@@ -2207,9 +2144,8 @@ static struct frec *allocate_frec(time_t
+ f->next = daemon->frec_list;
+ f->time = now;
+ f->sentto = NULL;
+- f->rfd4 = NULL;
++ f->rfds = NULL;
+ f->flags = 0;
+- f->rfd6 = NULL;
+ #ifdef HAVE_DNSSEC
+ f->dependent = NULL;
+ f->blocking_query = NULL;
+@@ -2221,46 +2157,192 @@ static struct frec *allocate_frec(time_t
+ return f;
+ }
+
+-struct randfd *allocate_rfd(int family)
++/* return a UDP socket bound to a random port, have to cope with straying into
++ occupied port nos and reserved ones. */
++static int random_sock(struct server *s)
++{
++ int fd;
++
++ if ((fd = socket(s->source_addr.sa.sa_family, SOCK_DGRAM, 0)) != -1)
++ {
++ if (local_bind(fd, &s->source_addr, s->interface, s->ifindex, 0))
++ return fd;
++
++ if (s->interface[0] == 0)
++ (void)prettyprint_addr(&s->source_addr, daemon->namebuff);
++ else
++ strcpy(daemon->namebuff, s->interface);
++
++ my_syslog(LOG_ERR, _("failed to bind server socket to %s: %s"),
++ daemon->namebuff, strerror(errno));
++ close(fd);
++ }
++
++ return -1;
++}
++
++/* compare source addresses and interface, serv2 can be null. */
++static int server_isequal(const struct server *serv1,
++ const struct server *serv2)
++{
++ return (serv2 &&
++ serv2->ifindex == serv1->ifindex &&
++ sockaddr_isequal(&serv2->source_addr, &serv1->source_addr) &&
++ strncmp(serv2->interface, serv1->interface, IF_NAMESIZE) == 0);
++}
++
++/* fdlp points to chain of randomfds already in use by transaction.
++ If there's already a suitable one, return it, else allocate a
++ new one and add it to the list.
++
++ Not leaking any resources in the face of allocation failures
++ is rather convoluted here.
++
++ Note that rfd->serv may be NULL, when a server goes away.
++*/
++int allocate_rfd(struct randfd_list **fdlp, struct server *serv)
+ {
+ static int finger = 0;
+- int i;
++ int i, j = 0;
++ struct randfd_list *rfl;
++ struct randfd *rfd = NULL;
++ int fd = 0;
++
++ /* If server has a pre-allocated fd, use that. */
++ if (serv->sfd)
++ return serv->sfd->fd;
++
++ /* existing suitable random port socket linked to this transaction? */
++ for (rfl = *fdlp; rfl; rfl = rfl->next)
++ if (server_isequal(serv, rfl->rfd->serv))
++ return rfl->rfd->fd;
++
++ /* No. need new link. */
++ if ((rfl = daemon->rfl_spare))
++ daemon->rfl_spare = rfl->next;
++ else if (!(rfl = whine_malloc(sizeof(struct randfd_list))))
++ return -1;
+
+ /* limit the number of sockets we have open to avoid starvation of
+ (eg) TFTP. Once we have a reasonable number, randomness should be OK */
+-
+ for (i = 0; i < RANDOM_SOCKS; i++)
+ if (daemon->randomsocks[i].refcount == 0)
+ {
+- if ((daemon->randomsocks[i].fd = random_sock(family)) == -1)
+- break;
+-
+- daemon->randomsocks[i].refcount = 1;
+- daemon->randomsocks[i].family = family;
+- return &daemon->randomsocks[i];
++ if ((fd = random_sock(serv)) != -1)
++ {
++ rfd = &daemon->randomsocks[i];
++ rfd->serv = serv;
++ rfd->fd = fd;
++ rfd->refcount = 1;
++ }
++ break;
+ }
+
+ /* No free ones or cannot get new socket, grab an existing one */
+- for (i = 0; i < RANDOM_SOCKS; i++)
++ if (!rfd)
++ for (j = 0; j < RANDOM_SOCKS; j++)
++ {
++ i = (j + finger) % RANDOM_SOCKS;
++ if (daemon->randomsocks[i].refcount != 0 &&
++ server_isequal(serv, daemon->randomsocks[i].serv) &&
++ daemon->randomsocks[i].refcount != 0xfffe)
++ {
++ finger = i + 1;
++ rfd = &daemon->randomsocks[i];
++ rfd->refcount++;
++ break;
++ }
++ }
++
++ if (j == RANDOM_SOCKS)
+ {
+- int j = (i+finger) % RANDOM_SOCKS;
+- if (daemon->randomsocks[j].refcount != 0 &&
+- daemon->randomsocks[j].family == family &&
+- daemon->randomsocks[j].refcount != 0xffff)
++ struct randfd_list *rfl_poll;
++
++ /* there are no free slots, and non with the same parameters we can piggy-back on.
++ We're going to have to allocate a new temporary record, distinguished by
++ refcount == 0xffff. This will exist in the frec randfd list, never be shared,
++ and be freed when no longer in use. It will also be held on
++ the daemon->rfl_poll list so the poll system can find it. */
++
++ if ((rfl_poll = daemon->rfl_spare))
++ daemon->rfl_spare = rfl_poll->next;
++ else
++ rfl_poll = whine_malloc(sizeof(struct randfd_list));
++
++ if (!rfl_poll ||
++ !(rfd = whine_malloc(sizeof(struct randfd))) ||
++ (fd = random_sock(serv)) == -1)
+ {
+- finger = j;
+- daemon->randomsocks[j].refcount++;
+- return &daemon->randomsocks[j];
++
++ /* Don't leak anything we may already have */
++ rfl->next = daemon->rfl_spare;
++ daemon->rfl_spare = rfl;
++
++ if (rfl_poll)
++ {
++ rfl_poll->next = daemon->rfl_spare;
++ daemon->rfl_spare = rfl_poll;
++ }
++
++ if (rfd)
++ free(rfd);
++
++ return -1; /* doom */
+ }
++
++ /* Note rfd->serv not set here, since it's not reused */
++ rfd->fd = fd;
++ rfd->refcount = 0xffff; /* marker for temp record */
++
++ rfl_poll->rfd = rfd;
++ rfl_poll->next = daemon->rfl_poll;
++ daemon->rfl_poll = rfl_poll;
+ }
+
+- return NULL; /* doom */
++ rfl->rfd = rfd;
++ rfl->next = *fdlp;
++ *fdlp = rfl;
++
++ return rfl->rfd->fd;
+ }
+
+-void free_rfd(struct randfd *rfd)
++void free_rfds(struct randfd_list **fdlp)
+ {
+- if (rfd && --(rfd->refcount) == 0)
+- close(rfd->fd);
++ struct randfd_list *tmp, *rfl, *poll, *next, **up;
++
++ for (rfl = *fdlp; rfl; rfl = tmp)
++ {
++ if (rfl->rfd->refcount == 0xffff || --(rfl->rfd->refcount) == 0)
++ close(rfl->rfd->fd);
++
++ /* temporary overflow record */
++ if (rfl->rfd->refcount == 0xffff)
++ {
++ free(rfl->rfd);
++
++ /* go through the link of all these by steam to delete.
++ This list is expected to be almost always empty. */
++ for (poll = daemon->rfl_poll, up = &daemon->rfl_poll; poll; poll = next)
++ {
++ next = poll->next;
++
++ if (poll->rfd == rfl->rfd)
++ {
++ *up = poll->next;
++ poll->next = daemon->rfl_spare;
++ daemon->rfl_spare = poll;
++ }
++ else
++ up = &poll->next;
++ }
++ }
++
++ tmp = rfl->next;
++ rfl->next = daemon->rfl_spare;
++ daemon->rfl_spare = rfl;
++ }
++
++ *fdlp = NULL;
+ }
+
+ static void free_frec(struct frec *f)
+@@ -2276,12 +2358,9 @@ static void free_frec(struct frec *f)
+ }
+
+ f->frec_src.next = NULL;
+- free_rfd(f->rfd4);
+- f->rfd4 = NULL;
++ free_rfds(&f->rfds);
+ f->sentto = NULL;
+ f->flags = 0;
+- free_rfd(f->rfd6);
+- f->rfd6 = NULL;
+
+ #ifdef HAVE_DNSSEC
+ if (f->stash)
+@@ -2389,26 +2468,39 @@ struct frec *get_new_frec(time_t now, in
+ }
+
+ /* crc is all-ones if not known. */
+-static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash)
++static struct frec *lookup_frec(unsigned short id, int fd, void *hash)
+ {
+ struct frec *f;
++ struct server *s;
++ int type;
++ struct randfd_list *fdl;
+
+ for(f = daemon->frec_list; f; f = f->next)
+ if (f->sentto && f->new_id == id &&
+ (memcmp(hash, f->hash, HASH_SIZE) == 0))
+ {
+ /* sent from random port */
+- if (family == AF_INET && f->rfd4 && f->rfd4->fd == fd)
++ for (fdl = f->rfds; fdl; fdl = fdl->next)
++ if (fdl->rfd->fd == fd)
+ return f;
++ }
+
+- if (family == AF_INET6 && f->rfd6 && f->rfd6->fd == fd)
+- return f;
++ /* Sent to upstream from socket associated with a server.
++ Note we have to iterate over all the possible servers, since they may
++ have different bound sockets. */
++ type = f->sentto->flags & SERV_TYPE;
++ s = f->sentto;
++ do {
++ if ((type == (s->flags & SERV_TYPE)) &&
++ (type != SERV_HAS_DOMAIN ||
++ (s->domain && hostname_isequal(f->sentto->domain, s->domain))) &&
++ !(s->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)) &&
++ s->sfd && s->sfd->fd == fd)
++ return f;
++
++ s = s->next ? s->next : daemon->servers;
++ } while (s != f->sentto);
+
+- /* sent to upstream from bound socket. */
+- if (f->sentto->sfd && f->sentto->sfd->fd == fd)
+- return f;
+- }
+-
+ return NULL;
+ }
+
+@@ -2454,30 +2546,26 @@ static struct frec *lookup_frec_by_query
+ void resend_query()
+ {
+ if (daemon->srv_save)
+- {
+- int fd;
+-
+- if (daemon->srv_save->sfd)
+- fd = daemon->srv_save->sfd->fd;
+- else if (daemon->rfd_save && daemon->rfd_save->refcount != 0)
+- fd = daemon->rfd_save->fd;
+- else
+- return;
+-
+- while(retry_send(sendto(fd, daemon->packet, daemon->packet_len, 0,
+- &daemon->srv_save->addr.sa,
+- sa_len(&daemon->srv_save->addr))));
+- }
++ while(retry_send(sendto(daemon->fd_save, daemon->packet, daemon->packet_len, 0,
++ &daemon->srv_save->addr.sa,
++ sa_len(&daemon->srv_save->addr))));
+ }
+
+ /* A server record is going away, remove references to it */
+ void server_gone(struct server *server)
+ {
+ struct frec *f;
++ int i;
+
+ for (f = daemon->frec_list; f; f = f->next)
+ if (f->sentto && f->sentto == server)
+ free_frec(f);
++
++ /* If any random socket refers to this server, NULL the reference.
++ No more references to the socket will be created in the future. */
++ for (i = 0; i < RANDOM_SOCKS; i++)
++ if (daemon->randomsocks[i].refcount != 0 && daemon->randomsocks[i].serv == server)
++ daemon->randomsocks[i].serv = NULL;
+
+ if (daemon->last_server == server)
+ daemon->last_server = NULL;
+Index: dnsmasq-2.81/src/loop.c
+===================================================================
+--- dnsmasq-2.81.orig/src/loop.c
++++ dnsmasq-2.81/src/loop.c
+@@ -22,6 +22,7 @@ static ssize_t loop_make_probe(u32 uid);
+ void loop_send_probes()
+ {
+ struct server *serv;
++ struct randfd_list *rfds = NULL;
+
+ if (!option_bool(OPT_LOOP_DETECT))
+ return;
+@@ -34,22 +35,15 @@ void loop_send_probes()
+ {
+ ssize_t len = loop_make_probe(serv->uid);
+ int fd;
+- struct randfd *rfd = NULL;
+
+- if (serv->sfd)
+- fd = serv->sfd->fd;
+- else
+- {
+- if (!(rfd = allocate_rfd(serv->addr.sa.sa_family)))
+- continue;
+- fd = rfd->fd;
+- }
++ if ((fd = allocate_rfd(&rfds, serv)) == -1)
++ continue;
+
+ while (retry_send(sendto(fd, daemon->packet, len, 0,
+ &serv->addr.sa, sa_len(&serv->addr))));
+-
+- free_rfd(rfd);
+ }
++
++ free_rfds(&rfds);
+ }
+
+ static ssize_t loop_make_probe(u32 uid)
+Index: dnsmasq-2.81/src/network.c
+===================================================================
+--- dnsmasq-2.81.orig/src/network.c
++++ dnsmasq-2.81/src/network.c
+@@ -545,6 +545,7 @@ int enumerate_interfaces(int reset)
+ #ifdef HAVE_AUTH
+ struct auth_zone *zone;
+ #endif
++ struct server *serv;
+
+ /* Do this max once per select cycle - also inhibits netlink socket use
+ in TCP child processes. */
+@@ -562,7 +563,21 @@ int enumerate_interfaces(int reset)
+
+ if ((param.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
+ return 0;
+-
++
++ /* iface indexes can change when interfaces are created/destroyed.
++ We use them in the main forwarding control path, when the path
++ to a server is specified by an interface, so cache them.
++ Update the cache here. */
++ for (serv = daemon->servers; serv; serv = serv->next)
++ if (strlen(serv->interface) != 0)
++ {
++ struct ifreq ifr;
++
++ safe_strncpy(ifr.ifr_name, serv->interface, IF_NAMESIZE);
++ if (ioctl(param.fd, SIOCGIFINDEX, &ifr) != -1)
++ serv->ifindex = ifr.ifr_ifindex;
++ }
++
+ /* Mark interfaces for garbage collection */
+ for (iface = daemon->interfaces; iface; iface = iface->next)
+ iface->found = 0;
+@@ -658,7 +673,7 @@ int enumerate_interfaces(int reset)
+
+ errno = errsave;
+ spare = param.spare;
+-
++
+ return ret;
+ }
+
+@@ -798,10 +813,10 @@ int tcp_interface(int fd, int af)
+ /* use mshdr so that the CMSDG_* macros are available */
+ msg.msg_control = daemon->packet;
+ msg.msg_controllen = len = daemon->packet_buff_sz;
+-
++
+ /* we overwrote the buffer... */
+ daemon->srv_save = NULL;
+-
++
+ if (af == AF_INET)
+ {
+ if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) != -1 &&
+@@ -1102,59 +1117,6 @@ void join_multicast(int dienow)
+ }
+ #endif
+
+-/* return a UDP socket bound to a random port, have to cope with straying into
+- occupied port nos and reserved ones. */
+-int random_sock(int family)
+-{
+- int fd;
+-
+- if ((fd = socket(family, SOCK_DGRAM, 0)) != -1)
+- {
+- union mysockaddr addr;
+- unsigned int ports_avail = ((unsigned short)daemon->max_port - (unsigned short)daemon->min_port) + 1;
+- int tries = ports_avail < 30 ? 3 * ports_avail : 100;
+-
+- memset(&addr, 0, sizeof(addr));
+- addr.sa.sa_family = family;
+-
+- /* don't loop forever if all ports in use. */
+-
+- if (fix_fd(fd))
+- while(tries--)
+- {
+- unsigned short port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail)));
+-
+- if (family == AF_INET)
+- {
+- addr.in.sin_addr.s_addr = INADDR_ANY;
+- addr.in.sin_port = port;
+-#ifdef HAVE_SOCKADDR_SA_LEN
+- addr.in.sin_len = sizeof(struct sockaddr_in);
+-#endif
+- }
+- else
+- {
+- addr.in6.sin6_addr = in6addr_any;
+- addr.in6.sin6_port = port;
+-#ifdef HAVE_SOCKADDR_SA_LEN
+- addr.in6.sin6_len = sizeof(struct sockaddr_in6);
+-#endif
+- }
+-
+- if (bind(fd, (struct sockaddr *)&addr, sa_len(&addr)) == 0)
+- return fd;
+-
+- if (errno != EADDRINUSE && errno != EACCES)
+- break;
+- }
+-
+- close(fd);
+- }
+-
+- return -1;
+-}
+-
+-
+ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp)
+ {
+ union mysockaddr addr_copy = *addr;
+@@ -1199,38 +1161,33 @@ int local_bind(int fd, union mysockaddr
+ return 1;
+ }
+
+-static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname)
++static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname, unsigned int ifindex)
+ {
+ struct serverfd *sfd;
+- unsigned int ifindex = 0;
+ int errsave;
+ int opt = 1;
+
+ /* when using random ports, servers which would otherwise use
+- the INADDR_ANY/port0 socket have sfd set to NULL */
+- if (!daemon->osport && intname[0] == 0)
++ the INADDR_ANY/port0 socket have sfd set to NULL, this is
++ anything without an explictly set source port. */
++ if (!daemon->osport)
+ {
+ errno = 0;
+
+ if (addr->sa.sa_family == AF_INET &&
+- addr->in.sin_addr.s_addr == INADDR_ANY &&
+ addr->in.sin_port == htons(0))
+ return NULL;
+
+ if (addr->sa.sa_family == AF_INET6 &&
+- memcmp(&addr->in6.sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0 &&
+ addr->in6.sin6_port == htons(0))
+ return NULL;
+ }
+
+- if (intname && strlen(intname) != 0)
+- ifindex = if_nametoindex(intname); /* index == 0 when not binding to an interface */
+-
+ /* may have a suitable one already */
+ for (sfd = daemon->sfds; sfd; sfd = sfd->next )
+- if (sockaddr_isequal(&sfd->source_addr, addr) &&
+- strcmp(intname, sfd->interface) == 0 &&
+- ifindex == sfd->ifindex)
++ if (ifindex == sfd->ifindex &&
++ sockaddr_isequal(&sfd->source_addr, addr) &&
++ strcmp(intname, sfd->interface) == 0)
+ return sfd;
+
+ /* need to make a new one. */
+@@ -1281,7 +1238,7 @@ void pre_allocate_sfds(void)
+ #ifdef HAVE_SOCKADDR_SA_LEN
+ addr.in.sin_len = sizeof(struct sockaddr_in);
+ #endif
+- if ((sfd = allocate_sfd(&addr, "")))
++ if ((sfd = allocate_sfd(&addr, "", 0)))
+ sfd->preallocated = 1;
+
+ memset(&addr, 0, sizeof(addr));
+@@ -1291,13 +1248,13 @@ void pre_allocate_sfds(void)
+ #ifdef HAVE_SOCKADDR_SA_LEN
+ addr.in6.sin6_len = sizeof(struct sockaddr_in6);
+ #endif
+- if ((sfd = allocate_sfd(&addr, "")))
++ if ((sfd = allocate_sfd(&addr, "", 0)))
+ sfd->preallocated = 1;
+ }
+
+ for (srv = daemon->servers; srv; srv = srv->next)
+ if (!(srv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND)) &&
+- !allocate_sfd(&srv->source_addr, srv->interface) &&
++ !allocate_sfd(&srv->source_addr, srv->interface, srv->ifindex) &&
+ errno != 0 &&
+ option_bool(OPT_NOWILD))
+ {
+@@ -1506,7 +1463,7 @@ void check_servers(void)
+
+ /* Do we need a socket set? */
+ if (!serv->sfd &&
+- !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface)) &&
++ !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface, serv->ifindex)) &&
+ errno != 0)
+ {
+ my_syslog(LOG_WARNING,
+Index: dnsmasq-2.81/src/option.c
+===================================================================
+--- dnsmasq-2.81.orig/src/option.c
++++ dnsmasq-2.81/src/option.c
+@@ -810,7 +810,8 @@ char *parse_server(char *arg, union myso
+ if (interface_opt)
+ {
+ #if defined(SO_BINDTODEVICE)
+- safe_strncpy(interface, interface_opt, IF_NAMESIZE);
++ safe_strncpy(interface, source, IF_NAMESIZE);
++ source = interface_opt;
+ #else
+ return _("interface binding not supported");
+ #endif
+Index: dnsmasq-2.81/src/tftp.c
+===================================================================
+--- dnsmasq-2.81.orig/src/tftp.c
++++ dnsmasq-2.81/src/tftp.c
+@@ -601,7 +601,7 @@ void check_tftp_listeners(time_t now)
+
+ /* we overwrote the buffer... */
+ daemon->srv_save = NULL;
+-
++
+ if ((len = get_block(daemon->packet, transfer)) == -1)
+ {
+ len = tftp_err_oops(daemon->packet, transfer->file->filename);
+Index: dnsmasq-2.81/src/util.c
+===================================================================
+--- dnsmasq-2.81.orig/src/util.c
++++ dnsmasq-2.81/src/util.c
+@@ -316,7 +316,7 @@ void *whine_malloc(size_t size)
+ return ret;
+ }
+
+-int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2)
++int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2)
+ {
+ if (s1->sa.sa_family == s2->sa.sa_family)
+ {
diff --git a/meta-networking/recipes-support/dnsmasq/dnsmasq_2.81.bb b/meta-networking/recipes-support/dnsmasq/dnsmasq_2.81.bb
index a1dc0f3a0..2fb389915 100644
--- a/meta-networking/recipes-support/dnsmasq/dnsmasq_2.81.bb
+++ b/meta-networking/recipes-support/dnsmasq/dnsmasq_2.81.bb
@@ -10,4 +10,5 @@ SRC_URI += "\
file://CVE-2020-25685-2.patch \
file://CVE-2020-25686-1.patch \
file://CVE-2020-25686-2.patch \
+ file://CVE-2021-3448.patch \
"