diff -u dnsmasq-2.80/debian/changelog dnsmasq-2.80/debian/changelog --- dnsmasq-2.80/debian/changelog +++ dnsmasq-2.80/debian/changelog @@ -1,3 +1,57 @@ +dnsmasq (2.80-1.1ubuntu1.6) focal; urgency=medium + + * src/cache.c: Apply 162e5e0062ce923c494cc64282f293f0ed64fc10 from + upstream GIT to fix bug in DNS non-terminal code, added in 2.80, + which could sometimes cause a NODATA rather than an NXDOMAIN + reply (LP: #1995260). + + -- Miriam EspaƱa Acebal Tue, 15 Nov 2022 10:35:15 +0100 + +dnsmasq (2.80-1.1ubuntu1.5) focal-security; urgency=medium + + * SECURITY UPDATE: Heap use after free + - debian/patches/CVE-2022-0934.patch: Fix write-after-free error in + DHCPv6 code in src/rfc3315.c. + - CVE-2022-0934 + + -- Leonidas Da Silva Barbosa Mon, 18 Apr 2022 12:25:39 -0300 + +dnsmasq (2.80-1.1ubuntu1.4) focal-security; urgency=medium + + * SECURITY UPDATE: fixed port use when specific server is requested + - 74d4fcd756a85bc1823232ea74334f7ccfb9d5d2 + - CVE-2021-3448 + + -- Marc Deslauriers Thu, 06 May 2021 12:34:24 -0400 + +dnsmasq (2.80-1.1ubuntu1.3) focal-security; urgency=medium + + * SECURITY REGRESSION: issue with multiple queries and issue with retries + (LP: #1916462) + - backport multiple upstream commits to fix regressions + + 04490bf622ac84891aad6f2dd2edf83725decdee + + 12af2b171de0d678d98583e2190789e544440e02 + + 3f535da79e7a42104543ef5c7b5fa2bed819a78b + + 25e63f1e56f5acdcf91893a1b92ad1e0f2f552d8 + + 141a26f979b4bc959d8e866a295e24f8cf456920 + + 305cb79c5754d5554729b18a2c06fe7ce699687a + + -- Marc Deslauriers Tue, 23 Feb 2021 07:58:11 -0500 + +dnsmasq (2.80-1.1ubuntu1.2) focal-security; urgency=medium + + * SECURITY UPDATE: Multiple security issues + - CVE-2020-25681: heap overflow in RRSets sorting + - CVE-2020-25682: buffer overflow in extracting names from DNS packets + - CVE-2020-25683: heap overflow in DNSSEC validation + - CVE-2020-25684: cache poisoning issue via address/port + - CVE-2020-25685: cache poisoning issue via weak hash + - CVE-2020-25686: birthday attack via incorrect existing requests check + - CVE-2020-25687: heap overflow in DNSSEC validation + - CVE-2019-14834: memory leak via DHCP response creation + + -- Marc Deslauriers Fri, 08 Jan 2021 09:59:59 -0500 + dnsmasq (2.80-1.1ubuntu1) focal; urgency=low * Merge from Debian unstable. Remaining changes: diff -u dnsmasq-2.80/src/crypto.c dnsmasq-2.80/src/crypto.c --- dnsmasq-2.80/src/crypto.c +++ dnsmasq-2.80/src/crypto.c @@ -23,6 +23,9 @@ #include #include #include +#endif + +#if defined(HAVE_DNSSEC) || defined(HAVE_NETTLEHASH) #include #include @@ -165,6 +168,10 @@ return 1; } + +#endif + +#ifdef HAVE_DNSSEC static int dnsmasq_rsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, unsigned char *digest, size_t digest_len, int algo) diff -u dnsmasq-2.80/src/dnsmasq.h dnsmasq-2.80/src/dnsmasq.h --- dnsmasq-2.80/src/dnsmasq.h +++ dnsmasq-2.80/src/dnsmasq.h @@ -153,7 +153,7 @@ #include #endif -#ifdef HAVE_DNSSEC +#if defined(HAVE_DNSSEC) || defined(HAVE_NETTLEHASH) # include #endif @@ -535,13 +535,20 @@ }; 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; @@ -648,25 +655,26 @@ #define FREC_DO_QUESTION 64 #define FREC_ADDED_PHEADER 128 #define FREC_TEST_PKTSZ 256 -#define FREC_HAS_EXTRADATA 512 +#define FREC_HAS_EXTRADATA 512 +#define FREC_HAS_PHEADER 1024 +#define FREC_NO_CACHE 2048 -#ifdef HAVE_DNSSEC -#define HASH_SIZE 20 /* SHA-1 digest size */ -#else -#define HASH_SIZE sizeof(int) -#endif +#define HASH_SIZE 32 /* SHA-256 digest size */ struct frec { - union mysockaddr source; - struct all_addr dest; + struct frec_src { + union mysockaddr source; + struct all_addr dest; + unsigned int iface, log_id; + int fd; + unsigned short orig_id; + struct frec_src *next; + } frec_src; struct server *sentto; /* NULL means free */ - struct randfd *rfd4; -#ifdef HAVE_IPV6 - struct randfd *rfd6; -#endif + struct randfd_list *rfds; unsigned int iface; - unsigned short orig_id, new_id; - int log_id, fd, forwardall, flags; + unsigned short new_id; + int forwardall, flags; time_t time; unsigned char *hash[HASH_SIZE]; #ifdef HAVE_DNSSEC @@ -1082,6 +1090,8 @@ int back_to_the_future; #endif struct frec *frec_list; + struct frec_src *free_frec_src; + int frec_src_count; struct serverfd *sfds; struct irec *interfaces; struct listener *listeners; @@ -1090,9 +1100,10 @@ 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]; 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 */ @@ -1207,7 +1218,6 @@ struct bogus_addr *baddr, time_t now); int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bogus_addr *baddr); int check_for_local_domain(char *name, time_t now); -unsigned int questions_crc(struct dns_header *header, size_t plen, char *name); size_t resize_packet(struct dns_header *header, size_t plen, unsigned char *pheader, size_t hlen); int add_resource_record(struct dns_header *header, char *limit, int *truncp, @@ -1235,9 +1245,11 @@ int check_unsigned, int *neganswer, int *nons); int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen); size_t filter_rrsigs(struct dns_header *header, size_t plen); -unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name); int setup_timestamp(void); +/* hash_questions.c */ +unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name); + /* crypto.c */ const struct nettle_hash *hash_find(char *name); int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp); @@ -1260,7 +1272,7 @@ 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); @@ -1310,7 +1322,7 @@ 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); @@ -1320,13 +1332,12 @@ union mysockaddr *to, struct 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); @@ -1636,7 +1647,7 @@ unsigned short udp_sz, int optno, unsigned char *opt, size_t optlen, int set_do, int replace); size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit); size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, - union mysockaddr *source, time_t now, int *check_subnet); + union mysockaddr *source, time_t now, int *check_subnet, int *cacheable); int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer); /* arp.c */ only in patch2: unchanged: --- dnsmasq-2.80.orig/Makefile +++ dnsmasq-2.80/Makefile @@ -53,7 +53,7 @@ dbus_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DBUS $(PKG_CONFIG) --cflags dbus-1` dbus_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DBUS $(PKG_CONFIG) --libs dbus-1` -ubus_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_UBUS $(PKG_CONFIG) --copy -lubox -lubus` +ubus_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_UBUS $(PKG_CONFIG) --copy '-lubox -lubus'` idn_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_IDN $(PKG_CONFIG) --cflags libidn` idn_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_IDN $(PKG_CONFIG) --libs libidn` idn2_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LIBIDN2 $(PKG_CONFIG) --cflags libidn2` @@ -62,8 +62,10 @@ ct_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CONFIG) --libs libnetfilter_conntrack` lua_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --cflags lua5.2` lua_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --libs lua5.2` -nettle_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --cflags nettle hogweed` -nettle_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --libs nettle hogweed` +nettle_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --cflags 'nettle hogweed' \ + HAVE_NETTLEHASH $(PKG_CONFIG) --cflags nettle` +nettle_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --libs 'nettle hogweed' \ + HAVE_NETTLEHASH $(PKG_CONFIG) --libs nettle` gmp_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC NO_GMP --copy -lgmp` sunos_libs = `if uname | grep SunOS >/dev/null 2>&1; then echo -lsocket -lnsl -lposix4; fi` version = -DVERSION='\"`$(top)/bld/get-version $(top)`\"' @@ -77,7 +79,8 @@ helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \ dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \ domain.o dnssec.o blockdata.o tables.o loop.o inotify.o \ - poll.o rrfilter.o edns0.o arp.o crypto.o dump.o ubus.o metrics.o + poll.o rrfilter.o edns0.o arp.o crypto.o dump.o ubus.o \ + metrics.o hash_questions.o hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ dns-protocol.h radv-protocol.h ip6addr.h metrics.h only in patch2: unchanged: --- dnsmasq-2.80.orig/bld/Android.mk +++ dnsmasq-2.80/bld/Android.mk @@ -11,7 +11,7 @@ radv.c slaac.c auth.c ipset.c domain.c \ dnssec.c dnssec-openssl.c blockdata.c tables.c \ loop.c inotify.c poll.c rrfilter.c edns0.c arp.c \ - crypto.c dump.c ubus.c + crypto.c dump.c ubus.c metrics.c hash_questions.c LOCAL_MODULE := dnsmasq only in patch2: unchanged: --- dnsmasq-2.80.orig/bld/pkg-wrapper +++ dnsmasq-2.80/bld/pkg-wrapper @@ -1,33 +1,35 @@ #!/bin/sh -search=$1 -shift -pkg=$1 -shift -op=$1 -shift - in=`cat` -if grep "^\#[[:space:]]*define[[:space:]]*$search" config.h >/dev/null 2>&1 || \ - echo $in | grep $search >/dev/null 2>&1; then +search() +{ + grep "^\#[[:space:]]*define[[:space:]]*$1" config.h >/dev/null 2>&1 || \ + echo $in | grep $1 >/dev/null 2>&1 +} + +while [ "$#" -gt 0 ]; do + search=$1 + pkg=$2 + op=$3 + lib=$4 + shift 4 +if search "$search"; then + # Nasty, nasty, in --copy, arg 2 is another config to search for, use with NO_GMP if [ $op = "--copy" ]; then - if grep "^\#[[:space:]]*define[[:space:]]*$pkg" config.h >/dev/null 2>&1 || \ - echo $in | grep $pkg >/dev/null 2>&1; then + if search "$pkg"; then pkg="" else - pkg="$*" + pkg="$lib" fi - elif grep "^\#[[:space:]]*define[[:space:]]*${search}_STATIC" config.h >/dev/null 2>&1 || \ - echo $in | grep ${search}_STATIC >/dev/null 2>&1; then - pkg=`$pkg --static $op $*` + elif search "${search}_STATIC"; then + pkg=`$pkg --static $op $lib` else - pkg=`$pkg $op $*` + pkg=`$pkg $op $lib` fi - if grep "^\#[[:space:]]*define[[:space:]]*${search}_STATIC" config.h >/dev/null 2>&1 || \ - echo $in | grep ${search}_STATIC >/dev/null 2>&1; then + if search "${search}_STATIC"; then if [ $op = "--libs" ] || [ $op = "--copy" ]; then echo "-Wl,-Bstatic $pkg -Wl,-Bdynamic" else @@ -38,3 +40,4 @@ fi fi +done only in patch2: unchanged: --- dnsmasq-2.80.orig/man/dnsmasq.8 +++ dnsmasq-2.80/man/dnsmasq.8 @@ -423,7 +423,7 @@ or domain parts, to upstream nameservers. If the name is not known from /etc/hosts or DHCP then a "not found" answer is returned. .TP -.B \-S, --local, --server=[/[]/[domain/]][[#][@|[#]] +.B \-S, --local, --server=[/[]/[domain/]][[#][@][@[#]] Specify IP address of upstream servers directly. Setting this flag does not suppress reading of /etc/resolv.conf, use \fB--no-resolv\fP to do that. If one or more optional domains are given, that server is used only for those domains @@ -484,7 +484,7 @@ part of the source address. Forcing queries to an interface is not implemented on all platforms supported by dnsmasq. .TP -.B --rev-server=/,[#][@|[#]] +.B --rev-server=/,[#][@][@[#]] This is functionally the same as .B --server, but provides some syntactic sugar to make specifying address-to-name queries easier. For example @@ -690,8 +690,8 @@ address information either. The default is zero for both IPv4 and IPv6. Note that upstream nameservers may be configured to return different results based on this information, but the dnsmasq cache -does not take account. If a dnsmasq instance is configured such that -different results may be encountered, caching should be disabled. +does not take account. Caching is therefore disabled for such replies, +unless the subnet address being added is constant. For example, .B --add-subnet=24,96 only in patch2: unchanged: --- dnsmasq-2.80.orig/src/cache.c +++ dnsmasq-2.80/src/cache.c @@ -659,6 +659,7 @@ if (!is_outdated_cname_pointer(crecp) && !is_expired(now, crecp) && (crecp->flags & F_FORWARD) && + !(crecp->flags & F_NXDOMAIN) && hostname_isequal(name, cache_get_name(crecp))) return 1; only in patch2: unchanged: --- dnsmasq-2.80.orig/src/config.h +++ dnsmasq-2.80/src/config.h @@ -117,6 +117,9 @@ define this to include the facility to act as an authoritative DNS server for one or more zones. +HAVE_NETTLEHASH + include just hash function from nettle, but no DNSSEC. + HAVE_DNSSEC include DNSSEC validator. @@ -185,6 +188,7 @@ /* #define HAVE_IDN */ /* #define HAVE_LIBIDN2 */ /* #define HAVE_CONNTRACK */ +/* #define HAVE_NETTLEHASH */ /* #define HAVE_DNSSEC */ @@ -449,6 +453,10 @@ "no-" #endif "auth " +#if !defined(HAVE_NETTLEHASH) && !defined(HAVE_DNSSEC) +"no-" +#endif +"nettlehash " #ifndef HAVE_DNSSEC "no-" #endif only in patch2: unchanged: --- dnsmasq-2.80.orig/src/dnsmasq.c +++ dnsmasq-2.80/src/dnsmasq.c @@ -1577,6 +1577,7 @@ { struct serverfd *serverfdp; struct listener *listener; + struct randfd_list *rfl; int wait = 0, i; #ifdef HAVE_TFTP @@ -1596,11 +1597,14 @@ 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 */ @@ -1631,17 +1635,22 @@ { struct serverfd *serverfdp; struct listener *listener; + struct randfd_list *rfl; int i; 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); for (listener = daemon->listeners; listener; listener = listener->next) { only in patch2: unchanged: --- dnsmasq-2.80.orig/src/dnssec.c +++ dnsmasq-2.80/src/dnssec.c @@ -222,138 +222,147 @@ && serial_compare_32(curtime, date_end) == SERIAL_LT; } -/* Return bytes of canonicalised rdata, when the return value is zero, the remaining - data, pointed to by *p, should be used raw. */ -static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen, - unsigned char **p, u16 **desc) +/* Return bytes of canonicalised rrdata one by one. + Init state->ip with the RR, and state->end with the end of same. + Init state->op to NULL. + Init state->desc to RR descriptor. + Init state->buff with a MAXDNAME * 2 buffer. + + After each call which returns 1, state->op points to the next byte of data. + On returning 0, the end has been reached. +*/ +struct rdata_state { + u16 *desc; + size_t c; + unsigned char *end, *ip, *op; + char *buff; +}; + +static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state *state) { - int d = **desc; + int d; - /* No more data needs mangling */ - if (d == (u16)-1) + if (state->op && state->c != 1) { - /* If there's more data than we have space for, just return what fits, - we'll get called again for more chunks */ - if (end - *p > bufflen) - { - memcpy(buff, *p, bufflen); - *p += bufflen; - return bufflen; - } - - return 0; + state->op++; + state->c--; + return 1; } - - (*desc)++; - - if (d == 0 && extract_name(header, plen, p, buff, 1, 0)) - /* domain-name, canonicalise */ - return to_wire(buff); - else - { - /* plain data preceding a domain-name, don't run off the end of the data */ - if ((end - *p) < d) - d = end - *p; + + while (1) + { + d = *(state->desc); - if (d != 0) + if (d == (u16)-1) { - memcpy(buff, *p, d); - *p += d; + /* all the bytes to the end. */ + if ((state->c = state->end - state->ip) != 0) + { + state->op = state->ip; + state->ip = state->end;; + } + else + return 0; + } + else + { + state->desc++; + + if (d == (u16)0) + { + /* domain-name, canonicalise */ + int len; + + if (!extract_name(header, plen, &state->ip, state->buff, 1, 0) || + (len = to_wire(state->buff)) == 0) + continue; + + state->c = len; + state->op = (unsigned char *)state->buff; + } + else + { + /* plain data preceding a domain-name, don't run off the end of the data */ + if ((state->end - state->ip) < d) + d = state->end - state->ip; + + if (d == 0) + continue; + + state->op = state->ip; + state->c = d; + state->ip += d; + } } - return d; + return 1; } } -/* Bubble sort the RRset into the canonical order. - Note that the byte-streams from two RRs may get unsynced: consider - RRs which have two domain-names at the start and then other data. - The domain-names may have different lengths in each RR, but sort equal - - ------------ - |abcde|fghi| - ------------ - |abcd|efghi| - ------------ - - leaving the following bytes as deciding the order. Hence the nasty left1 and left2 variables. -*/ +/* Bubble sort the RRset into the canonical order. */ static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, unsigned char **rrset, char *buff1, char *buff2) { - int swap, quit, i, j; + int swap, i, j; do { for (swap = 0, i = 0; i < rrsetidx-1; i++) { - int rdlen1, rdlen2, left1, left2, len1, len2, len, rc; - u16 *dp1, *dp2; - unsigned char *end1, *end2; + int rdlen1, rdlen2; + struct rdata_state state1, state2; + /* Note that these have been determined to be OK previously, so we don't need to check for NULL return here. */ - unsigned char *p1 = skip_name(rrset[i], header, plen, 10); - unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10); - - p1 += 8; /* skip class, type, ttl */ - GETSHORT(rdlen1, p1); - end1 = p1 + rdlen1; - - p2 += 8; /* skip class, type, ttl */ - GETSHORT(rdlen2, p2); - end2 = p2 + rdlen2; - - dp1 = dp2 = rr_desc; - - for (quit = 0, left1 = 0, left2 = 0, len1 = 0, len2 = 0; !quit;) + state1.ip = skip_name(rrset[i], header, plen, 10); + state2.ip = skip_name(rrset[i+1], header, plen, 10); + state1.op = state2.op = NULL; + state1.buff = buff1; + state2.buff = buff2; + state1.desc = state2.desc = rr_desc; + + state1.ip += 8; /* skip class, type, ttl */ + GETSHORT(rdlen1, state1.ip); + if (!CHECK_LEN(header, state1.ip, plen, rdlen1)) + return rrsetidx; /* short packet */ + state1.end = state1.ip + rdlen1; + + state2.ip += 8; /* skip class, type, ttl */ + GETSHORT(rdlen2, state2.ip); + if (!CHECK_LEN(header, state2.ip, plen, rdlen2)) + return rrsetidx; /* short packet */ + state2.end = state2.ip + rdlen2; + + while (1) { - if (left1 != 0) - memmove(buff1, buff1 + len1 - left1, left1); + int ok1, ok2; - if ((len1 = get_rdata(header, plen, end1, buff1 + left1, (MAXDNAME * 2) - left1, &p1, &dp1)) == 0) - { - quit = 1; - len1 = end1 - p1; - memcpy(buff1 + left1, p1, len1); - } - len1 += left1; - - if (left2 != 0) - memmove(buff2, buff2 + len2 - left2, left2); - - if ((len2 = get_rdata(header, plen, end2, buff2 + left2, (MAXDNAME *2) - left2, &p2, &dp2)) == 0) - { - quit = 1; - len2 = end2 - p2; - memcpy(buff2 + left2, p2, len2); - } - len2 += left2; - - if (len1 > len2) - left1 = len1 - len2, left2 = 0, len = len2; - else - left2 = len2 - len1, left1 = 0, len = len1; - - rc = (len == 0) ? 0 : memcmp(buff1, buff2, len); - - if (rc > 0 || (rc == 0 && quit && len1 > len2)) - { - unsigned char *tmp = rrset[i+1]; - rrset[i+1] = rrset[i]; - rrset[i] = tmp; - swap = quit = 1; - } - else if (rc == 0 && quit && len1 == len2) + 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 (rc < 0) - quit = 1; + 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); @@ -549,15 +558,18 @@ wire_len = to_wire(keyname); hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname); from_wire(keyname); + +#define RRBUFLEN 300 /* Most RRs are smaller than this. */ for (i = 0; i < rrsetidx; ++i) { - int seg; - unsigned char *end, *cp; - u16 len, *dp; + int j; + struct rdata_state state; + u16 len; + unsigned char rrbuf[RRBUFLEN]; p = rrset[i]; - + if (!extract_name(header, plen, &p, name, 1, 10)) return STAT_BOGUS; @@ -566,12 +578,11 @@ /* if more labels than in RRsig name, hash *. 4035 5.3.2 */ if (labels < name_labels) { - int k; - for (k = name_labels - labels; k != 0; k--) + for (j = name_labels - labels; j != 0; j--) { while (*name_start != '.' && *name_start != 0) name_start++; - if (k != 1 && *name_start == '.') + if (j != 1 && *name_start == '.') name_start++; } @@ -592,24 +603,44 @@ if (!CHECK_LEN(header, p, plen, rdlen)) return STAT_BOGUS; - end = p + rdlen; - - /* canonicalise rdata and calculate length of same, use name buffer as workspace. - Note that name buffer is twice MAXDNAME long in DNSSEC mode. */ - cp = p; - dp = rr_desc; - for (len = 0; (seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)) != 0; len += seg); - len += end - cp; - len = htons(len); + /* 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) + { + 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; + } + } + } - /* Now canonicalise again and digest. */ - cp = p; - dp = rr_desc; - while ((seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp))) - hash->update(ctx, seg, (unsigned char *)name); - if (cp != end) - hash->update(ctx, end - cp, cp); + if (j != 0) + hash->update(ctx, j, rrbuf); } hash->digest(ctx, hash->digest_size, digest); @@ -2059,35 +2090,4 @@ return ret; } -unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name) -{ - int q; - unsigned int len; - unsigned char *p = (unsigned char *)(header+1); - const struct nettle_hash *hash; - void *ctx; - unsigned char *digest; - - if (!(hash = hash_find("sha1")) || !hash_init(hash, &ctx, &digest)) - return NULL; - - for (q = ntohs(header->qdcount); q != 0; q--) - { - if (!extract_name(header, plen, &p, name, 1, 4)) - break; /* bad packet */ - - len = to_wire(name); - hash->update(ctx, len, (unsigned char *)name); - /* CRC the class and type as well */ - hash->update(ctx, 4, p); - - p += 4; - if (!CHECK_LEN(header, p, plen, 0)) - break; /* bad packet */ - } - - hash->digest(ctx, hash->digest_size, digest); - return digest; -} - #endif /* HAVE_DNSSEC */ only in patch2: unchanged: --- dnsmasq-2.80.orig/src/edns0.c +++ dnsmasq-2.80/src/edns0.c @@ -264,7 +264,8 @@ out[3] = char64(in[2]); } -static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *l3, time_t now) +static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit, + union mysockaddr *l3, time_t now, int *cacheablep) { int maclen, replace = 2; /* can't get mac address, just delete any incoming. */ unsigned char mac[DHCP_CHADDR_MAX]; @@ -273,6 +274,7 @@ if ((maclen = find_mac(l3, mac, 1, now)) == 6) { replace = 1; + *cacheablep = 0; if (option_bool(OPT_MAC_HEX)) print_mac(encode, mac, maclen); @@ -288,14 +290,18 @@ } -static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *l3, time_t now) +static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit, + union mysockaddr *l3, time_t now, int *cacheablep) { int maclen; unsigned char mac[DHCP_CHADDR_MAX]; if ((maclen = find_mac(l3, mac, 1, now)) != 0) - plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0, 0); - + { + *cacheablep = 0; + plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0, 0); + } + return plen; } @@ -319,7 +325,7 @@ return &addr->in.sin_addr; } -static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source) +static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source, int *cacheablep) { /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */ @@ -327,6 +333,8 @@ void *addrp = NULL; int sa_family = source->sa.sa_family; + int cacheable = 0; + opt->source_netmask = 0; opt->scope_netmask = 0; @@ -338,6 +346,7 @@ { sa_family = daemon->add_subnet6->addr.sa.sa_family; addrp = get_addrp(&daemon->add_subnet6->addr, sa_family); + cacheable = 1; } else addrp = &source->in6.sin6_addr; @@ -351,6 +360,7 @@ { sa_family = daemon->add_subnet4->addr.sa.sa_family; addrp = get_addrp(&daemon->add_subnet4->addr, sa_family); + cacheable = 1; /* Address is constant */ } else addrp = &source->in.sin_addr; @@ -362,8 +372,6 @@ opt->family = htons(1); #endif - len = 0; - if (addrp && opt->source_netmask != 0) { len = ((opt->source_netmask - 1) >> 3) + 1; @@ -371,18 +379,26 @@ if (opt->source_netmask & 7) opt->addr[len-1] &= 0xff << (8 - (opt->source_netmask & 7)); } + else + { + cacheable = 1; /* No address ever supplied. */ + len = 0; + } + + if (cacheablep) + *cacheablep = cacheable; return len + 4; } -static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source) +static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source, int *cacheable) { /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */ int len; struct subnet_opt opt; - len = calc_subnet_opt(&opt, source); + len = calc_subnet_opt(&opt, source, cacheable); return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0, 0); } @@ -395,18 +411,18 @@ unsigned char *p; int code, i, rdlen; - calc_len = calc_subnet_opt(&opt, peer); - - if (!(p = skip_name(pseudoheader, header, plen, 10))) - return 1; + calc_len = calc_subnet_opt(&opt, peer, NULL); - p += 8; /* skip UDP length and RCODE */ - - GETSHORT(rdlen, p); - if (!CHECK_LEN(header, p, plen, rdlen)) - return 1; /* bad packet */ - - /* check if option there */ + if (!(p = skip_name(pseudoheader, header, plen, 10))) + return 1; + + p += 8; /* skip UDP length and RCODE */ + + GETSHORT(rdlen, p); + if (!CHECK_LEN(header, p, plen, rdlen)) + return 1; /* bad packet */ + + /* check if option there */ for (i = 0; i + 4 < rdlen; i += len + 4) { GETSHORT(code, p); @@ -424,24 +440,28 @@ return 1; } +/* Set *check_subnet if we add a client subnet option, which needs to checked + in the reply. Set *cacheable to zero if we add an option which the answer + may depend on. */ size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, - union mysockaddr *source, time_t now, int *check_subnet) + union mysockaddr *source, time_t now, int *check_subnet, int *cacheable) { *check_subnet = 0; - + *cacheable = 1; + if (option_bool(OPT_ADD_MAC)) - plen = add_mac(header, plen, limit, source, now); + plen = add_mac(header, plen, limit, source, now, cacheable); if (option_bool(OPT_MAC_B64) || option_bool(OPT_MAC_HEX)) - plen = add_dns_client(header, plen, limit, source, now); - + plen = add_dns_client(header, plen, limit, source, now, cacheable); + if (daemon->dns_client_id) plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_NOMCPEID, (unsigned char *)daemon->dns_client_id, strlen(daemon->dns_client_id), 0, 1); if (option_bool(OPT_CLIENT_SUBNET)) { - plen = add_source_addr(header, plen, limit, source); + plen = add_source_addr(header, plen, limit, source, cacheable); *check_subnet = 1; } only in patch2: unchanged: --- dnsmasq-2.80.orig/src/forward.c +++ dnsmasq-2.80/src/forward.c @@ -16,10 +16,9 @@ #include "dnsmasq.h" -static struct frec *lookup_frec(unsigned short id, void *hash); -static struct frec *lookup_frec_by_sender(unsigned short id, - union mysockaddr *addr, - void *hash); +static struct frec *lookup_frec(unsigned short id, int fd, void *hash); +static struct frec *lookup_frec_by_query(void *hash, unsigned int flags); + static unsigned short get_id(void); static void free_frec(struct frec *f); @@ -259,20 +258,69 @@ int type = SERV_DO_DNSSEC, norebind = 0; struct all_addr *addrp = NULL; unsigned int flags = 0; + unsigned int fwd_flags = 0; struct server *start = NULL; -#ifdef HAVE_DNSSEC void *hash = hash_questions(header, plen, daemon->namebuff); +#ifdef HAVE_DNSSEC int do_dnssec = 0; -#else - unsigned int crc = questions_crc(header, plen, daemon->namebuff); - void *hash = &crc; #endif unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); unsigned char *oph = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL); (void)do_bit; + + if (header->hb4 & HB4_CD) + fwd_flags |= FREC_CHECKING_DISABLED; + if (ad_reqd) + fwd_flags |= FREC_AD_QUESTION; + if (oph) + fwd_flags |= FREC_HAS_PHEADER; +#ifdef HAVE_DNSSEC + if (do_bit) + fwd_flags |= FREC_DO_QUESTION; +#endif + + /* Check for retry on existing query */ + if (!forward && (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; + + /* Existing query, but from new source, just add this + client to the list that will get the reply.*/ + if (!src) + { + /* 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) + return 0; + + 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; + } + } - /* may be no servers available. */ - if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))) + /* retry existing query */ + 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. @@ -300,29 +348,18 @@ 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", (struct all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec"); -#ifdef HAVE_IPV6 - else - log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (struct all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec"); -#endif - - if (forward->sentto->sfd) - fd = forward->sentto->sfd->fd; - else + if ((fd = allocate_rfd(&forward->rfds, forward->sentto)) != -1) { -#ifdef HAVE_IPV6 - 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", (struct all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec"); else -#endif - fd = forward->rfd4->fd; + log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (struct 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 @@ -346,6 +383,8 @@ } else { + /* new query */ + if (gotname) flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); @@ -354,21 +393,23 @@ #endif type &= ~SERV_DO_DNSSEC; + /* may be no servers available. */ if (daemon->servers && !flags) forward = get_new_frec(now, NULL, 0); /* table full - flags == 0, return REFUSED */ if (forward) { - forward->source = *udpaddr; - forward->dest = *dst_addr; - forward->iface = dst_iface; - forward->orig_id = ntohs(header->id); + forward->frec_src.source = *udpaddr; + forward->frec_src.orig_id = ntohs(header->id); + forward->frec_src.dest = *dst_addr; + forward->frec_src.iface = dst_iface; + forward->frec_src.next = NULL; + forward->frec_src.fd = udpfd; forward->new_id = get_id(); - forward->fd = udpfd; memcpy(forward->hash, hash, HASH_SIZE); forward->forwardall = 0; - forward->flags = 0; + forward->flags = fwd_flags; if (norebind) forward->flags |= FREC_NOREBIND; if (header->hb4 & HB4_CD) @@ -418,18 +459,21 @@ if (!flags && forward) { struct server *firstsentto = start; - int subnet, forwarded = 0; + int subnet, cacheable, forwarded = 0; size_t edns0_len; unsigned char *pheader; /* If a query is retried, use the log_id for the retry when logging the answer. */ - forward->log_id = daemon->log_id; + forward->frec_src.log_id = daemon->log_id; - plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->source, now, &subnet); + plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet, &cacheable); if (subnet) forward->flags |= FREC_HAS_SUBNET; - + + if (!cacheable) + forward->flags |= FREC_NO_CACHE; + #ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID) && do_dnssec) { @@ -460,50 +504,27 @@ 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 - { -#ifdef HAVE_IPV6 - 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 -#endif - { - 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)) @@ -535,6 +556,7 @@ /* 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"); @@ -553,7 +575,7 @@ break; forward->forwardall++; } - } + } if (!(start = start->next)) start = daemon->servers; @@ -566,7 +588,7 @@ return 1; /* could not send on, prepare to return */ - header->id = htons(forward->orig_id); + header->id = htons(forward->frec_src.orig_id); free_frec(forward); /* cancel */ } @@ -617,7 +639,7 @@ } } #endif - + if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign, NULL))) { /* Get extended RCODE. */ @@ -768,7 +790,7 @@ } /* 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 */ @@ -780,19 +802,13 @@ size_t nn; struct server *server; void *hash; -#ifndef HAVE_DNSSEC - unsigned int crc; -#endif /* packet buffer overwritten */ daemon->srv_save = NULL; - + /* Determine the address of the server replying so that we can mark that as good */ - serveraddr.sa.sa_family = family; -#ifdef HAVE_IPV6 if (serveraddr.sa.sa_family == AF_INET6) serveraddr.in6.sin6_flowinfo = 0; -#endif header = (struct dns_header *)daemon->packet; @@ -812,14 +828,9 @@ if (difftime(now, server->pktsz_reduced) > UDP_TEST_TIME) server->edns_pktsz = daemon->edns_pktsz; -#ifdef HAVE_DNSSEC hash = hash_questions(header, n, daemon->namebuff); -#else - hash = &crc; - crc = questions_crc(header, n, daemon->namebuff); -#endif - if (!(forward = lookup_frec(ntohs(header->id), hash))) + if (!(forward = lookup_frec(ntohs(header->id), fd, hash))) return; #ifdef HAVE_DUMPFILE @@ -829,8 +840,8 @@ /* log_query gets called indirectly all over the place, so pass these in global variables - sorry. */ - daemon->log_display_id = forward->log_id; - daemon->log_source_addr = &forward->source; + daemon->log_display_id = forward->frec_src.log_id; + daemon->log_source_addr = &forward->frec_src.source; if (daemon->ignore_addr && RCODE(header) == NOERROR && check_for_ignored_address(header, n, daemon->ignore_addr)) @@ -874,28 +885,8 @@ } - if (start->sfd) - fd = start->sfd->fd; - else - { -#ifdef HAVE_IPV6 - 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 -#endif - { - /* 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; while (retry_send(sendto(fd, (char *)header, plen, 0, &start->addr.sa, sa_len(&start->addr)))); @@ -1098,10 +1089,8 @@ } new->sentto = server; - new->rfd4 = NULL; -#ifdef HAVE_IPV6 - new->rfd6 = NULL; -#endif + new->rfds = NULL; + new->frec_src.next = NULL; new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA); new->forwardall = 0; @@ -1131,8 +1120,7 @@ querystr("dnssec-query", querytype)); #endif - if ((hash = hash_questions(header, nn, daemon->namebuff))) - memcpy(new->hash, hash, HASH_SIZE); + memcpy(new->hash, hash_questions(header, nn, daemon->namebuff), HASH_SIZE); new->new_id = get_id(); header->id = htons(new->new_id); /* Save query for retransmission */ @@ -1142,33 +1130,14 @@ /* Don't resend this. */ daemon->srv_save = NULL; - if (server->sfd) - fd = server->sfd->fd; - else - { - fd = -1; -#ifdef HAVE_IPV6 - if (server->addr.sa.sa_family == AF_INET6) - { - if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6))) - fd = new->rfd6->fd; - } - else -#endif - { - 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. */ if (option_bool(OPT_CONNTRACK)) { unsigned int mark; - if (get_incoming_mark(&orig->source, &orig->dest, 0, &mark)) + if (get_incoming_mark(&orig->frec_src.source, &orig->frec_src.dest, 0, &mark)) setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); } #endif @@ -1238,12 +1207,19 @@ header->hb4 |= HB4_CD; else header->hb4 &= ~HB4_CD; + + /* Never cache answers which are contingent on the source or MAC address EDSN0 option, + since the cache is ignorant of such things. */ + if (forward->flags & FREC_NO_CACHE) + no_cache_dnssec = 1; if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer, forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, - forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->source))) + forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->frec_src.source))) { - header->id = htons(forward->orig_id); + struct frec_src *src; + + header->id = htons(forward->frec_src.orig_id); header->hb4 |= HB4_RA; /* recursion if available */ #ifdef HAVE_DNSSEC /* We added an EDNSO header for the purpose of getting DNSSEC RRs, and set the value of the UDP payload size @@ -1259,13 +1235,26 @@ } #endif + for (src = &forward->frec_src; src; src = src->next) + { + header->id = htons(src->orig_id); + #ifdef HAVE_DUMPFILE - dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &forward->source); + dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source); #endif - - send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, - &forward->source, &forward->dest, forward->iface); + + send_from(src->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, + &src->source, &src->dest, src->iface); + + if (option_bool(OPT_EXTRALOG) && src != &forward->frec_src) + { + daemon->log_display_id = src->log_id; + daemon->log_source_addr = &src->source; + log_query(F_UPSTREAM, "query", NULL, "duplicate"); + } + } } + free_frec(forward); /* cancel */ } } @@ -1782,7 +1771,7 @@ int local_auth = 0; #endif int checking_disabled, do_bit, added_pheader = 0, have_pseudoheader = 0; - int check_subnet, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; + int check_subnet, cacheable, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; size_t m; unsigned short qtype; unsigned int gotname; @@ -1957,7 +1946,7 @@ char *domain = NULL; unsigned char *oph = find_pseudoheader(header, size, NULL, NULL, NULL, NULL); - size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet); + size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet, &cacheable); if (gotname) flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); @@ -1989,15 +1978,9 @@ if (!flags && last_server) { struct server *firstsendto = NULL; -#ifdef HAVE_DNSSEC - unsigned char *newhash, hash[HASH_SIZE]; - if ((newhash = hash_questions(header, (unsigned int)size, daemon->namebuff))) - memcpy(hash, newhash, HASH_SIZE); - else - memset(hash, 0, HASH_SIZE); -#else - unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff); -#endif + unsigned char hash[HASH_SIZE]; + memcpy(hash, hash_questions(header, (unsigned int)size, daemon->namebuff), HASH_SIZE); + /* Loop round available servers until we succeed in connecting to one. Note that this code subtly ensures that consecutive queries on this connection which can go to the same server, do so. */ @@ -2122,21 +2105,17 @@ /* If the crc of the question section doesn't match the crc we sent, then someone might be attempting to insert bogus values into the cache by sending replies containing questions and bogus answers. */ -#ifdef HAVE_DNSSEC - newhash = hash_questions(header, (unsigned int)m, daemon->namebuff); - if (!newhash || memcmp(hash, newhash, HASH_SIZE) != 0) + if (memcmp(hash, hash_questions(header, (unsigned int)m, daemon->namebuff), HASH_SIZE) != 0) { m = 0; break; } -#else - if (crc != questions_crc(header, (unsigned int)m, daemon->namebuff)) - { - m = 0; - break; - } -#endif + /* Never cache answers which are contingent on the source or MAC address EDSN0 option, + since the cache is ignorant of such things. */ + if (!cacheable) + no_cache_dnssec = 1; + m = process_reply(header, now, last_server, (unsigned int)m, option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer, ad_reqd, do_bit, added_pheader, check_subnet, &peer_addr); @@ -2173,11 +2152,8 @@ f->next = daemon->frec_list; f->time = now; f->sentto = NULL; - f->rfd4 = NULL; + f->rfds = NULL; f->flags = 0; -#ifdef HAVE_IPV6 - f->rfd6 = NULL; -#endif #ifdef HAVE_DNSSEC f->dependent = NULL; f->blocking_query = NULL; @@ -2189,60 +2165,211 @@ 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) { - static int finger = 0; - int i; + 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, 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 */ } - } - return NULL; /* 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; + } + + 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) { - free_rfd(f->rfd4); - f->rfd4 = NULL; + struct frec_src *last; + + /* add back to freelist if not the record builtin to every frec. */ + for (last = f->frec_src.next; last && last->next; last = last->next) ; + if (last) + { + last->next = daemon->free_frec_src; + daemon->free_frec_src = f->frec_src.next; + } + + f->frec_src.next = NULL; + free_rfds(&f->rfds); f->sentto = NULL; f->flags = 0; -#ifdef HAVE_IPV6 - free_rfd(f->rfd6); - f->rfd6 = NULL; -#endif - #ifdef HAVE_DNSSEC if (f->stash) { @@ -2348,66 +2475,92 @@ } /* crc is all-ones if not known. */ -static struct frec *lookup_frec(unsigned short id, 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 && - (!hash || memcmp(hash, f->hash, HASH_SIZE) == 0)) - return f; + (memcmp(hash, f->hash, HASH_SIZE) == 0)) + { + /* sent from random port */ + for (fdl = f->rfds; fdl; fdl = fdl->next) + if (fdl->rfd->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); + } return NULL; } -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) { struct frec *f; + + /* FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below + ensures that no frec created for internal DNSSEC query can be returned here. + + Similarly FREC_NO_CACHE is never set in flags, so a query which is + contigent on a particular source address EDNS0 option will never be matched. */ + +#define FLAGMASK (FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION \ + | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE) for(f = daemon->frec_list; f; f = f->next) if (f->sentto && - f->orig_id == id && - memcmp(hash, f->hash, HASH_SIZE) == 0 && - sockaddr_isequal(&f->source, addr)) + (f->flags & FLAGMASK) == flags && + memcmp(hash, f->hash, HASH_SIZE) == 0) return f; - + return NULL; } - + /* Send query packet again, if we can. */ 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; - + if (daemon->srv_save == server) daemon->srv_save = NULL; } @@ -2416,12 +2569,20 @@ static unsigned short get_id(void) { unsigned short ret = 0; + struct frec *f; - do - ret = rand16(); - while (lookup_frec(ret, NULL)); - - return ret; + while (1) + { + ret = rand16(); + + /* ensure id is unique. */ + for (f = daemon->frec_list; f; f = f->next) + if (f->sentto && f->new_id == ret) + break; + + if (!f) + return ret; + } } only in patch2: unchanged: --- dnsmasq-2.80.orig/src/hash_questions.c +++ dnsmasq-2.80/src/hash_questions.c @@ -0,0 +1,281 @@ +/* Copyright (c) 2012-2020 Simon Kelley + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +/* Hash the question section. This is used to safely detect query + retransmission and to detect answers to questions we didn't ask, which + might be poisoning attacks. Note that we decode the name rather + than CRC the raw bytes, since replies might be compressed differently. + We ignore case in the names for the same reason. + + The hash used is SHA-256. If we're building with DNSSEC support, + we use the Nettle cypto library. If not, we prefer not to + add a dependency on Nettle, and use a stand-alone implementaion. +*/ + +#include "dnsmasq.h" + +#if defined(HAVE_DNSSEC) || defined(HAVE_NETTLEHASH) +unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name) +{ + int q; + unsigned char *p = (unsigned char *)(header+1); + const struct nettle_hash *hash; + void *ctx; + unsigned char *digest; + + if (!(hash = hash_find("sha256")) || !hash_init(hash, &ctx, &digest)) + { + /* don't think this can ever happen. */ + static unsigned char dummy[HASH_SIZE]; + static int warned = 0; + + if (warned) + my_syslog(LOG_ERR, _("Failed to create SHA-256 hash object")); + warned = 1; + + return dummy; + } + + for (q = ntohs(header->qdcount); q != 0; q--) + { + char *cp, c; + + if (!extract_name(header, plen, &p, name, 1, 4)) + break; /* bad packet */ + + for (cp = name; (c = *cp); cp++) + if (c >= 'A' && c <= 'Z') + *cp += 'a' - 'A'; + + hash->update(ctx, cp - name, (unsigned char *)name); + /* CRC the class and type as well */ + hash->update(ctx, 4, p); + + p += 4; + if (!CHECK_LEN(header, p, plen, 0)) + break; /* bad packet */ + } + + hash->digest(ctx, hash->digest_size, digest); + return digest; +} + +#else /* HAVE_DNSSEC */ + +#define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest +typedef unsigned char BYTE; // 8-bit byte +typedef unsigned int WORD; // 32-bit word, change to "long" for 16-bit machines + +typedef struct { + BYTE data[64]; + WORD datalen; + unsigned long long bitlen; + WORD state[8]; +} SHA256_CTX; + +static void sha256_init(SHA256_CTX *ctx); +static void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len); +static void sha256_final(SHA256_CTX *ctx, BYTE hash[]); + + +unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name) +{ + int q; + unsigned char *p = (unsigned char *)(header+1); + SHA256_CTX ctx; + static BYTE digest[SHA256_BLOCK_SIZE]; + + sha256_init(&ctx); + + for (q = ntohs(header->qdcount); q != 0; q--) + { + char *cp, c; + + if (!extract_name(header, plen, &p, name, 1, 4)) + break; /* bad packet */ + + for (cp = name; (c = *cp); cp++) + if (c >= 'A' && c <= 'Z') + *cp += 'a' - 'A'; + + sha256_update(&ctx, (BYTE *)name, cp - name); + /* CRC the class and type as well */ + sha256_update(&ctx, (BYTE *)p, 4); + + p += 4; + if (!CHECK_LEN(header, p, plen, 0)) + break; /* bad packet */ + } + + sha256_final(&ctx, digest); + return (unsigned char *)digest; +} + +/* Code from here onwards comes from https://github.com/B-Con/crypto-algorithms + and was written by Brad Conte (brad@bradconte.com), to whom all credit is given. + + This code is in the public domain, and the copyright notice at the head of this + file does not apply to it. +*/ + + +/****************************** MACROS ******************************/ +#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b)))) +#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b)))) + +#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z))) +#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) +#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22)) +#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25)) +#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3)) +#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10)) + +/**************************** VARIABLES *****************************/ +static const WORD k[64] = { + 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5, + 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174, + 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da, + 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967, + 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85, + 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070, + 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3, + 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2 +}; + +/*********************** FUNCTION DEFINITIONS ***********************/ +static void sha256_transform(SHA256_CTX *ctx, const BYTE data[]) +{ + WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64]; + + for (i = 0, j = 0; i < 16; ++i, j += 4) + m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]); + for ( ; i < 64; ++i) + m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16]; + + a = ctx->state[0]; + b = ctx->state[1]; + c = ctx->state[2]; + d = ctx->state[3]; + e = ctx->state[4]; + f = ctx->state[5]; + g = ctx->state[6]; + h = ctx->state[7]; + + for (i = 0; i < 64; ++i) + { + t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i]; + t2 = EP0(a) + MAJ(a,b,c); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + + ctx->state[0] += a; + ctx->state[1] += b; + ctx->state[2] += c; + ctx->state[3] += d; + ctx->state[4] += e; + ctx->state[5] += f; + ctx->state[6] += g; + ctx->state[7] += h; +} + +static void sha256_init(SHA256_CTX *ctx) +{ + ctx->datalen = 0; + ctx->bitlen = 0; + ctx->state[0] = 0x6a09e667; + ctx->state[1] = 0xbb67ae85; + ctx->state[2] = 0x3c6ef372; + ctx->state[3] = 0xa54ff53a; + ctx->state[4] = 0x510e527f; + ctx->state[5] = 0x9b05688c; + ctx->state[6] = 0x1f83d9ab; + ctx->state[7] = 0x5be0cd19; +} + +static void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len) +{ + WORD i; + + for (i = 0; i < len; ++i) + { + ctx->data[ctx->datalen] = data[i]; + ctx->datalen++; + if (ctx->datalen == 64) { + sha256_transform(ctx, ctx->data); + ctx->bitlen += 512; + ctx->datalen = 0; + } + } +} + +static void sha256_final(SHA256_CTX *ctx, BYTE hash[]) +{ + WORD i; + + i = ctx->datalen; + + // Pad whatever data is left in the buffer. + if (ctx->datalen < 56) + { + ctx->data[i++] = 0x80; + while (i < 56) + ctx->data[i++] = 0x00; + } + else + { + ctx->data[i++] = 0x80; + while (i < 64) + ctx->data[i++] = 0x00; + sha256_transform(ctx, ctx->data); + memset(ctx->data, 0, 56); + } + + // Append to the padding the total message's length in bits and transform. + ctx->bitlen += ctx->datalen * 8; + ctx->data[63] = ctx->bitlen; + ctx->data[62] = ctx->bitlen >> 8; + ctx->data[61] = ctx->bitlen >> 16; + ctx->data[60] = ctx->bitlen >> 24; + ctx->data[59] = ctx->bitlen >> 32; + ctx->data[58] = ctx->bitlen >> 40; + ctx->data[57] = ctx->bitlen >> 48; + ctx->data[56] = ctx->bitlen >> 56; + sha256_transform(ctx, ctx->data); + + // Since this implementation uses little endian byte ordering and SHA uses big endian, + // reverse all the bytes when copying the final state to the output hash. + for (i = 0; i < 4; ++i) + { + hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff; + hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff; + hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff; + hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff; + hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff; + hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff; + hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff; + hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff; + } +} + +#endif only in patch2: unchanged: --- dnsmasq-2.80.orig/src/helper.c +++ dnsmasq-2.80/src/helper.c @@ -82,7 +82,8 @@ pid_t pid; int i, pipefd[2]; struct sigaction sigact; - + unsigned char *alloc_buff = NULL; + /* create the pipe through which the main program sends us commands, then fork our process. */ if (pipe(pipefd) == -1 || !fix_fd(pipefd[1]) || (pid = fork()) == -1) @@ -188,11 +189,16 @@ struct script_data data; char *p, *action_str, *hostname = NULL, *domain = NULL; unsigned char *buf = (unsigned char *)daemon->namebuff; - unsigned char *end, *extradata, *alloc_buff = NULL; + unsigned char *end, *extradata; int is6, err = 0; int pipeout[2]; - free(alloc_buff); + /* Free rarely-allocated memory from previous iteration. */ + if (alloc_buff) + { + free(alloc_buff); + alloc_buff = NULL; + } /* we read zero bytes when pipe closed: this is our signal to exit */ if (!read_write(pipefd[0], (unsigned char *)&data, sizeof(data), 1)) only in patch2: unchanged: --- dnsmasq-2.80.orig/src/loop.c +++ dnsmasq-2.80/src/loop.c @@ -22,6 +22,7 @@ void loop_send_probes() { struct server *serv; + struct randfd_list *rfds = NULL; if (!option_bool(OPT_LOOP_DETECT)) return; @@ -34,29 +35,22 @@ { 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) { struct dns_header *header = (struct dns_header *)daemon->packet; unsigned char *p = (unsigned char *)(header+1); - + /* packet buffer overwritten */ daemon->srv_save = NULL; only in patch2: unchanged: --- dnsmasq-2.80.orig/src/network.c +++ dnsmasq-2.80/src/network.c @@ -565,7 +565,8 @@ #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. */ @@ -583,6 +584,20 @@ 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; @@ -680,7 +695,7 @@ errno = errsave; spare = param.spare; - + return ret; } @@ -819,10 +834,10 @@ /* 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; - + daemon->srv_save = NULL; + if (af == AF_INET) { if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) != -1 && @@ -1129,77 +1144,48 @@ } #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 - } -#ifdef HAVE_IPV6 - 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 - } -#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; + unsigned short port; + int tries = 1, done = 0; + unsigned int ports_avail = ((unsigned short)daemon->max_port - (unsigned short)daemon->min_port) + 1; + + if (addr_copy.sa.sa_family == AF_INET) + port = addr_copy.in.sin_port; + else + port = addr_copy.in6.sin6_port; /* cannot set source _port_ for TCP connections. */ if (is_tcp) + port = 0; + else if (port == 0) + { + /* Bind a random port within the range given by min-port and max-port */ + tries = ports_avail < 30 ? 3 * ports_avail : 100; + port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail))); + } + + while (tries--) { if (addr_copy.sa.sa_family == AF_INET) - addr_copy.in.sin_port = 0; -#ifdef HAVE_IPV6 + addr_copy.in.sin_port = port; else - addr_copy.in6.sin6_port = 0; -#endif + addr_copy.in6.sin6_port = port; + + if (bind(fd, (struct sockaddr *)&addr_copy, sa_len(&addr_copy)) != -1) + { + done = 1; + break; + } + + if (errno != EADDRINUSE && errno != EACCES) + return 0; + + port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail))); } - - if (bind(fd, (struct sockaddr *)&addr_copy, sa_len(&addr_copy)) == -1) + + if (!done) return 0; if (!is_tcp && ifindex > 0) @@ -1229,40 +1215,35 @@ 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; #ifdef HAVE_IPV6 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; #endif } - 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. */ @@ -1313,7 +1294,7 @@ #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; #ifdef HAVE_IPV6 memset(&addr, 0, sizeof(addr)); @@ -1323,14 +1304,14 @@ #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; #endif } 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)) { @@ -1539,7 +1520,7 @@ /* 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, only in patch2: unchanged: --- dnsmasq-2.80.orig/src/option.c +++ dnsmasq-2.80/src/option.c @@ -810,7 +810,8 @@ 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 only in patch2: unchanged: --- dnsmasq-2.80.orig/src/rfc1035.c +++ dnsmasq-2.80/src/rfc1035.c @@ -335,55 +335,6 @@ return ansp; } -/* CRC the question section. This is used to safely detect query - retransmission and to detect answers to questions we didn't ask, which - might be poisoning attacks. Note that we decode the name rather - than CRC the raw bytes, since replies might be compressed differently. - We ignore case in the names for the same reason. Return all-ones - if there is not question section. */ -#ifndef HAVE_DNSSEC -unsigned int questions_crc(struct dns_header *header, size_t plen, char *name) -{ - int q; - unsigned int crc = 0xffffffff; - unsigned char *p1, *p = (unsigned char *)(header+1); - - for (q = ntohs(header->qdcount); q != 0; q--) - { - if (!extract_name(header, plen, &p, name, 1, 4)) - return crc; /* bad packet */ - - for (p1 = (unsigned char *)name; *p1; p1++) - { - int i = 8; - char c = *p1; - - if (c >= 'A' && c <= 'Z') - c += 'a' - 'A'; - - crc ^= c << 24; - while (i--) - crc = crc & 0x80000000 ? (crc << 1) ^ 0x04c11db7 : crc << 1; - } - - /* CRC the class and type as well */ - for (p1 = p; p1 < p+4; p1++) - { - int i = 8; - crc ^= *p1 << 24; - while (i--) - crc = crc & 0x80000000 ? (crc << 1) ^ 0x04c11db7 : crc << 1; - } - - p += 4; - if (!CHECK_LEN(header, p, plen, 0)) - return crc; /* bad packet */ - } - - return crc; -} -#endif - size_t resize_packet(struct dns_header *header, size_t plen, unsigned char *pheader, size_t hlen) { unsigned char *ansp = skip_questions(header, plen); only in patch2: unchanged: --- dnsmasq-2.80.orig/src/rfc3315.c +++ dnsmasq-2.80/src/rfc3315.c @@ -36,9 +36,9 @@ #endif }; -static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz, +static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t sz, struct in6_addr *client_addr, int is_unicast, time_t now); -static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_t sz, int is_unicast, time_t now); +static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbuff, size_t sz, int is_unicast, time_t now); static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_opts); static void log6_packet(struct state *state, char *type, struct in6_addr *addr, char *string); static void log6_quiet(struct state *state, char *type, struct in6_addr *addr, char *string); @@ -108,12 +108,12 @@ } /* This cost me blood to write, it will probably cost you blood to understand - srk. */ -static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz, +static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t sz, struct in6_addr *client_addr, int is_unicast, time_t now) { void *end = inbuff + sz; void *opts = inbuff + 34; - int msg_type = *((unsigned char *)inbuff); + int msg_type = *inbuff; unsigned char *outmsgtypep; void *opt; struct dhcp_vendor *vendor; @@ -239,15 +239,15 @@ return 1; } -static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_t sz, int is_unicast, time_t now) +static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbuff, size_t sz, int is_unicast, time_t now) { void *opt; - int i, o, o1, start_opts; + int i, o, o1, start_opts, start_msg; struct dhcp_opt *opt_cfg; struct dhcp_netid *tagif; struct dhcp_config *config = NULL; struct dhcp_netid known_id, iface_id, v6_id; - unsigned char *outmsgtypep; + unsigned char outmsgtype; struct dhcp_vendor *vendor; struct dhcp_context *context_tmp; struct dhcp_mac *mac_opt; @@ -283,12 +283,13 @@ v6_id.next = state->tags; state->tags = &v6_id; - /* copy over transaction-id, and save pointer to message type */ - if (!(outmsgtypep = put_opt6(inbuff, 4))) + start_msg = save_counter(-1); + /* copy over transaction-id */ + if (!put_opt6(inbuff, 4)) return 0; start_opts = save_counter(-1); - state->xid = outmsgtypep[3] | outmsgtypep[2] << 8 | outmsgtypep[1] << 16; - + state->xid = inbuff[3] | inbuff[2] << 8 | inbuff[1] << 16; + /* We're going to be linking tags from all context we use. mark them as unused so we don't link one twice and break the list */ for (context_tmp = state->context; context_tmp; context_tmp = context_tmp->current) @@ -334,7 +335,7 @@ (msg_type == DHCP6REQUEST || msg_type == DHCP6RENEW || msg_type == DHCP6RELEASE || msg_type == DHCP6DECLINE)) { - *outmsgtypep = DHCP6REPLY; + outmsgtype = DHCP6REPLY; o1 = new_opt6(OPTION6_STATUS_CODE); put_opt6_short(DHCP6USEMULTI); put_opt6_string("Use multicast"); @@ -632,11 +633,11 @@ struct dhcp_netid *solicit_tags; struct dhcp_context *c; - *outmsgtypep = DHCP6ADVERTISE; + outmsgtype = DHCP6ADVERTISE; if (opt6_find(state->packet_options, state->end, OPTION6_RAPID_COMMIT, 0)) { - *outmsgtypep = DHCP6REPLY; + outmsgtype = DHCP6REPLY; state->lease_allocate = 1; o = new_opt6(OPTION6_RAPID_COMMIT); end_opt6(o); @@ -892,7 +893,7 @@ int start = save_counter(-1); /* set reply message type */ - *outmsgtypep = DHCP6REPLY; + outmsgtype = DHCP6REPLY; state->lease_allocate = 1; log6_quiet(state, "DHCPREQUEST", NULL, ignore ? _("ignored") : NULL); @@ -1009,7 +1010,7 @@ case DHCP6RENEW: { /* set reply message type */ - *outmsgtypep = DHCP6REPLY; + outmsgtype = DHCP6REPLY; log6_quiet(state, "DHCPRENEW", NULL, NULL); @@ -1122,7 +1123,7 @@ int good_addr = 0; /* set reply message type */ - *outmsgtypep = DHCP6REPLY; + outmsgtype = DHCP6REPLY; log6_quiet(state, "DHCPCONFIRM", NULL, NULL); @@ -1186,7 +1187,7 @@ log6_quiet(state, "DHCPINFORMATION-REQUEST", NULL, ignore ? _("ignored") : state->hostname); if (ignore) return 0; - *outmsgtypep = DHCP6REPLY; + outmsgtype = DHCP6REPLY; tagif = add_options(state, 1); break; } @@ -1195,7 +1196,7 @@ case DHCP6RELEASE: { /* set reply message type */ - *outmsgtypep = DHCP6REPLY; + outmsgtype = DHCP6REPLY; log6_quiet(state, "DHCPRELEASE", NULL, NULL); @@ -1260,7 +1261,7 @@ case DHCP6DECLINE: { /* set reply message type */ - *outmsgtypep = DHCP6REPLY; + outmsgtype = DHCP6REPLY; log6_quiet(state, "DHCPDECLINE", NULL, NULL); @@ -1339,7 +1340,12 @@ } } - + + /* Fill in the message type. Note that we store the offset, + not a direct pointer, since the packet memory may have been + reallocated. */ + ((unsigned char *)(daemon->outpacket.iov_base))[start_msg] = outmsgtype; + log_tags(tagif, state->xid); log6_opts(0, state->xid, daemon->outpacket.iov_base + start_opts, daemon->outpacket.iov_base + save_counter(-1)); only in patch2: unchanged: --- dnsmasq-2.80.orig/src/tftp.c +++ dnsmasq-2.80/src/tftp.c @@ -96,7 +96,7 @@ if ((len = recvmsg(listen->tftpfd, &msg, 0)) < 2) return; - + /* Can always get recvd interface for IPv6 */ if (!check_dest) { @@ -609,7 +609,7 @@ /* 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); only in patch2: unchanged: --- dnsmasq-2.80.orig/src/util.c +++ dnsmasq-2.80/src/util.c @@ -312,7 +312,7 @@ 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) {