Compare commits

..
6 Commits
Author SHA1 Message Date
Simon Kelley e6841ea2e0 Add posix-timezone and tzdb-timezone DHCPv6 options.
They are already in place for DHCPv4.
2022-12-04 22:00:54 +00:00
Simon Kelley e939b45c9f Handle malformed DNS replies better.
If we detect that that reply from usptream is malformed,
transform it into a SERVFAIL reply before sending to the
original requestor.
2022-11-26 22:19:29 +00:00
Brad Smith e3068ed111 Fix warning in cache.c 2022-11-26 21:48:17 +00:00
Dominik Derigs efbf80be58 Make max staleness of stale cache entries configurable and default to one day. 2022-11-26 21:18:34 +00:00
Petr Menšík 022ad63f0c Fix use-after-free in mark_servers() 2022-11-26 18:49:21 +00:00
Petr Menšík 02f8754339 fixup! Handle DS records for unsupported crypto algorithms. 2022-11-22 22:51:11 +00:00
11 changed files with 81 additions and 32 deletions

No files matched your search

+3
View File
@@ -59,6 +59,9 @@ version 2.88
needed is O(n^2). Handle this case more intelligently. needed is O(n^2). Handle this case more intelligently.
Thanks to Ye Zhou for spotting the problem and an initial patch. Thanks to Ye Zhou for spotting the problem and an initial patch.
If we detect that a DNS reply from upstream is malformed don't
return it to the requestor; send a SEVFAIL rcode instead.
version 2.87 version 2.87
Allow arbitrary prefix lengths in --rev-server and Allow arbitrary prefix lengths in --rev-server and
+3 -2
View File
@@ -828,11 +828,12 @@ name on successive queries, for load-balancing. This turns off that
behaviour, so that the records are always returned in the order behaviour, so that the records are always returned in the order
that they are received from upstream. that they are received from upstream.
.TP .TP
.B --use-stale-cache .B --use-stale-cache[=<max TTL excess in s>]
When set, if a DNS name exists in the cache, but its time-to-live has expired, dnsmasq will return the data anyway. (It attempts to refresh the When set, if a DNS name exists in the cache, but its time-to-live has expired, dnsmasq will return the data anyway. (It attempts to refresh the
data with an upstream query after returning the stale data.) This can improve speed and reliability. It comes at the expense data with an upstream query after returning the stale data.) This can improve speed and reliability. It comes at the expense
of sometimes returning out-of-date data and less efficient cache utilisation, since old data cannot be flushed when its TTL expires, so the cache becomes of sometimes returning out-of-date data and less efficient cache utilisation, since old data cannot be flushed when its TTL expires, so the cache becomes
strictly least-recently-used. mostly least-recently-used. To mitigate issues caused by massively outdated DNS replies, the maximum overaging of cached records can be specified in seconds
(defaulting to not serve anything older than one day). Setting the TTL excess time to zero will serve stale cache data regardless how long it has expired.
.TP .TP
.B \-0, --dns-forward-max=<queries> .B \-0, --dns-forward-max=<queries>
Set the maximum number of concurrent DNS queries. The default value is Set the maximum number of concurrent DNS queries. The default value is
+14 -4
View File
@@ -380,9 +380,17 @@ static int is_outdated_cname_pointer(struct crec *crecp)
static int is_expired(time_t now, struct crec *crecp) static int is_expired(time_t now, struct crec *crecp)
{ {
/* Don't dump expired entries if we're using them, cache becomes strictly LRU in that case. /* Don't dump expired entries if they are within the accepted timeout range.
Never use expired DS or DNSKEY entries. */ The cache becomes approx. LRU. Never use expired DS or DNSKEY entries.
if (option_bool(OPT_STALE_CACHE) && !(crecp->flags & (F_DS | F_DNSKEY))) Possible values for daemon->cache_max_expiry:
-1 == serve cached content regardless how long ago it expired
0 == the option is disabled, expired content isn't served
<n> == serve cached content only if it expire less than <n> seconds
ago (where n is a positive integer) */
if (daemon->cache_max_expiry != 0 &&
(daemon->cache_max_expiry == -1 ||
difftime(now, crecp->ttd) < daemon->cache_max_expiry) &&
!(crecp->flags & (F_DS | F_DNSKEY)))
return 0; return 0;
if (crecp->flags & F_IMMORTAL) if (crecp->flags & F_IMMORTAL)
@@ -1762,7 +1770,7 @@ void dump_cache(time_t now)
daemon->cachesize, daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED], daemon->metrics[METRIC_DNS_CACHE_INSERTED]); daemon->cachesize, daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED], daemon->metrics[METRIC_DNS_CACHE_INSERTED]);
my_syslog(LOG_INFO, _("queries forwarded %u, queries answered locally %u"), my_syslog(LOG_INFO, _("queries forwarded %u, queries answered locally %u"),
daemon->metrics[METRIC_DNS_QUERIES_FORWARDED], daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]); daemon->metrics[METRIC_DNS_QUERIES_FORWARDED], daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]);
if (option_bool(OPT_STALE_CACHE)) if (daemon->cache_max_expiry != 0)
my_syslog(LOG_INFO, _("queries answered from stale cache %u"), daemon->metrics[METRIC_DNS_STALE_ANSWERED]); my_syslog(LOG_INFO, _("queries answered from stale cache %u"), daemon->metrics[METRIC_DNS_STALE_ANSWERED]);
#ifdef HAVE_AUTH #ifdef HAVE_AUTH
my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->metrics[METRIC_DNS_AUTH_ANSWERED]); my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->metrics[METRIC_DNS_AUTH_ANSWERED]);
@@ -1889,7 +1897,9 @@ void dump_cache(time_t now)
char *record_source(unsigned int index) char *record_source(unsigned int index)
{ {
struct hostsfile *ah; struct hostsfile *ah;
#ifdef HAVE_INOTIFY
struct dyndir *dd; struct dyndir *dd;
#endif
if (index == SRC_CONFIG) if (index == SRC_CONFIG)
return "config"; return "config";
+1
View File
@@ -60,6 +60,7 @@
#define LOOP_TEST_DOMAIN "test" /* domain for loop testing, "test" is reserved by RFC 2606 and won't therefore clash */ #define LOOP_TEST_DOMAIN "test" /* domain for loop testing, "test" is reserved by RFC 2606 and won't therefore clash */
#define LOOP_TEST_TYPE T_TXT #define LOOP_TEST_TYPE T_TXT
#define DEFAULT_FAST_RETRY 1000 /* ms, default delay before fast retry */ #define DEFAULT_FAST_RETRY 1000 /* ms, default delay before fast retry */
#define STALE_CACHE_EXPIRY 86400 /* 1 day in secs, default maximum expiry time for stale cache data */
/* compile-time options: uncomment below to enable or do eg. /* compile-time options: uncomment below to enable or do eg.
make COPTS=-DHAVE_BROKEN_RTC make COPTS=-DHAVE_BROKEN_RTC
+2
View File
@@ -722,6 +722,8 @@ static const struct opttab_t opttab6[] = {
{ "sntp-server", 31, OT_ADDR_LIST }, { "sntp-server", 31, OT_ADDR_LIST },
{ "information-refresh-time", 32, OT_TIME }, { "information-refresh-time", 32, OT_TIME },
{ "FQDN", 39, OT_INTERNAL | OT_RFC1035_NAME }, { "FQDN", 39, OT_INTERNAL | OT_RFC1035_NAME },
{ "posix-timezone", 41, OT_NAME }, /* RFC 4833, Sec. 3 */
{ "tzdb-timezone", 42, OT_NAME }, /* RFC 4833, Sec. 3 */
{ "ntp-server", 56, 0 /* OT_ADDR_LIST | OT_RFC1035_NAME */ }, { "ntp-server", 56, 0 /* OT_ADDR_LIST | OT_RFC1035_NAME */ },
{ "bootfile-url", 59, OT_NAME }, { "bootfile-url", 59, OT_NAME },
{ "bootfile-param", 60, OT_CSTRING }, { "bootfile-param", 60, OT_CSTRING },
+3 -3
View File
@@ -280,9 +280,8 @@ struct event_desc {
#define OPT_FILTER_AAAA 68 #define OPT_FILTER_AAAA 68
#define OPT_STRIP_ECS 69 #define OPT_STRIP_ECS 69
#define OPT_STRIP_MAC 70 #define OPT_STRIP_MAC 70
#define OPT_STALE_CACHE 71 #define OPT_NORR 71
#define OPT_NORR 72 #define OPT_LAST 72
#define OPT_LAST 73
#define OPTION_BITS (sizeof(unsigned int)*8) #define OPTION_BITS (sizeof(unsigned int)*8)
#define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) ) #define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
@@ -1201,6 +1200,7 @@ extern struct daemon {
unsigned long soa_sn, soa_refresh, soa_retry, soa_expiry; unsigned long soa_sn, soa_refresh, soa_retry, soa_expiry;
u32 metrics[__METRIC_MAX]; u32 metrics[__METRIC_MAX];
int fast_retry_time, fast_retry_timeout; int fast_retry_time, fast_retry_timeout;
int cache_max_expiry;
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
struct ds_config *ds; struct ds_config *ds;
char *timestamp_file; char *timestamp_file;
+1 -1
View File
@@ -1068,7 +1068,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
algo = *p++; algo = *p++;
digest = *p++; digest = *p++;
if (!ds_digest_name(digest) || !ds_digest_name(digest)) if (!ds_digest_name(digest) || !algo_digest_name(algo))
{ {
a.log.keytag = keytag; a.log.keytag = keytag;
a.log.algo = algo; a.log.algo = algo;
+5 -3
View File
@@ -559,7 +559,7 @@ static int maybe_free_servers = 0;
/* Must be called before add_update_server() to set daemon->servers_tail */ /* Must be called before add_update_server() to set daemon->servers_tail */
void mark_servers(int flag) void mark_servers(int flag)
{ {
struct server *serv, **up; struct server *serv, *next, **up;
maybe_free_servers = !!flag; maybe_free_servers = !!flag;
@@ -580,11 +580,13 @@ void mark_servers(int flag)
1) numerous and 2) not reloaded often. We just delete 1) numerous and 2) not reloaded often. We just delete
and recreate. */ and recreate. */
if (flag) if (flag)
for (serv = daemon->local_domains, up = &daemon->local_domains; serv; serv = serv->next) for (serv = daemon->local_domains, up = &daemon->local_domains; serv; serv = next)
{ {
next = serv->next;
if (serv->flags & flag) if (serv->flags & flag)
{ {
*up = serv->next; *up = next;
free(serv->domain); free(serv->domain);
free(serv); free(serv);
} }
+11 -1
View File
@@ -821,12 +821,22 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
n = rrfilter(header, n, RRFILTER_AAAA); n = rrfilter(header, n, RRFILTER_AAAA);
} }
if (extract_addresses(header, n, daemon->namebuff, now, ipsets, nftsets, is_sign, check_rebind, no_cache, cache_secure, &doctored)) switch (extract_addresses(header, n, daemon->namebuff, now, ipsets, nftsets, is_sign, check_rebind, no_cache, cache_secure, &doctored))
{ {
case 1:
my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff); my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
munged = 1; munged = 1;
cache_secure = 0; cache_secure = 0;
ede = EDE_BLOCKED; ede = EDE_BLOCKED;
break;
/* extract_addresses() found a malformed answer. */
case 2:
munged = 1;
SET_RCODE(header, SERVFAIL);
cache_secure = 0;
ede = EDE_OTHER;
break;
} }
if (doctored) if (doctored)
+20 -2
View File
@@ -373,7 +373,7 @@ static const struct myoption opts[] =
{ "quiet-tftp", 0, 0, LOPT_QUIET_TFTP }, { "quiet-tftp", 0, 0, LOPT_QUIET_TFTP },
{ "port-limit", 1, 0, LOPT_RANDPORT_LIM }, { "port-limit", 1, 0, LOPT_RANDPORT_LIM },
{ "fast-dns-retry", 2, 0, LOPT_FAST_RETRY }, { "fast-dns-retry", 2, 0, LOPT_FAST_RETRY },
{ "use-stale-cache", 0, 0 , LOPT_STALE_CACHE }, { "use-stale-cache", 2, 0 , LOPT_STALE_CACHE },
{ NULL, 0, 0, 0 } { NULL, 0, 0, 0 }
}; };
@@ -431,7 +431,7 @@ static struct {
{ 'M', ARG_DUP, "<bootp opts>", gettext_noop("Specify BOOTP options to DHCP server."), NULL }, { 'M', ARG_DUP, "<bootp opts>", gettext_noop("Specify BOOTP options to DHCP server."), NULL },
{ 'n', OPT_NO_POLL, NULL, gettext_noop("Do NOT poll %s file, reload only on SIGHUP."), RESOLVFILE }, { 'n', OPT_NO_POLL, NULL, gettext_noop("Do NOT poll %s file, reload only on SIGHUP."), RESOLVFILE },
{ 'N', OPT_NO_NEG, NULL, gettext_noop("Do NOT cache failed search results."), NULL }, { 'N', OPT_NO_NEG, NULL, gettext_noop("Do NOT cache failed search results."), NULL },
{ LOPT_STALE_CACHE, OPT_STALE_CACHE, NULL, gettext_noop("Use expired cache data for faster reply."), NULL }, { LOPT_STALE_CACHE, ARG_ONE, "[=<max_expired>]", gettext_noop("Use expired cache data for faster reply."), NULL },
{ 'o', OPT_ORDER, NULL, gettext_noop("Use nameservers strictly in the order given in %s."), RESOLVFILE }, { 'o', OPT_ORDER, NULL, gettext_noop("Use nameservers strictly in the order given in %s."), RESOLVFILE },
{ 'O', ARG_DUP, "<optspec>", gettext_noop("Specify options to be sent to DHCP clients."), NULL }, { 'O', ARG_DUP, "<optspec>", gettext_noop("Specify options to be sent to DHCP clients."), NULL },
{ LOPT_FORCE, ARG_DUP, "<optspec>", gettext_noop("DHCP option sent even if the client does not request it."), NULL}, { LOPT_FORCE, ARG_DUP, "<optspec>", gettext_noop("DHCP option sent even if the client does not request it."), NULL},
@@ -5151,6 +5151,24 @@ err:
break; break;
} }
case LOPT_STALE_CACHE:
{
int max_expiry = STALE_CACHE_EXPIRY;
if (arg)
{
/* Don't accept negative TTLs here, they'd have the counter-intuitive
side-effect of evicting cache records before they expire */
if (!atoi_check(arg, &max_expiry) || max_expiry < 0)
ret_err(gen_err);
/* Store "serve expired forever" as -1 internally, the option isn't
active for daemon->cache_max_expiry == 0 */
if (max_expiry == 0)
max_expiry = -1;
}
daemon->cache_max_expiry = max_expiry;
break;
}
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
case LOPT_DNSSEC_STAMP: /* --dnssec-timestamp */ case LOPT_DNSSEC_STAMP: /* --dnssec-timestamp */
daemon->timestamp_file = opt_string_alloc(arg); daemon->timestamp_file = opt_string_alloc(arg);
+18 -16
View File
@@ -538,7 +538,9 @@ static int print_txt(struct dns_header *header, const size_t qlen, char *name,
/* Note that the following code can create CNAME chains that don't point to a real record, /* Note that the following code can create CNAME chains that don't point to a real record,
either because of lack of memory, or lack of SOA records. These are treated by the cache code as either because of lack of memory, or lack of SOA records. These are treated by the cache code as
expired and cleaned out that way. expired and cleaned out that way.
Return 1 if we reject an address because it look like part of dns-rebinding attack. */ Return 1 if we reject an address because it look like part of dns-rebinding attack.
Return 2 if the packet is malformed.
*/
int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now,
struct ipsets *ipsets, struct ipsets *nftsets, int is_sign, int check_rebind, struct ipsets *ipsets, struct ipsets *nftsets, int is_sign, int check_rebind,
int no_cache_dnssec, int secure, int *doctored) int no_cache_dnssec, int secure, int *doctored)
@@ -589,7 +591,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
namep = p = (unsigned char *)(header+1); namep = p = (unsigned char *)(header+1);
if (ntohs(header->qdcount) != 1 || !extract_name(header, qlen, &p, name, 1, 4)) if (ntohs(header->qdcount) != 1 || !extract_name(header, qlen, &p, name, 1, 4))
return 0; /* bad packet */ return 2; /* bad packet */
GETSHORT(qtype, p); GETSHORT(qtype, p);
GETSHORT(qclass, p); GETSHORT(qclass, p);
@@ -607,13 +609,13 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
{ {
cname_loop: cname_loop:
if (!(p1 = skip_questions(header, qlen))) if (!(p1 = skip_questions(header, qlen)))
return 0; return 2;
for (j = 0; j < ntohs(header->ancount); j++) for (j = 0; j < ntohs(header->ancount); j++)
{ {
int secflag = 0; int secflag = 0;
if (!(res = extract_name(header, qlen, &p1, name, 0, 10))) if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
return 0; /* bad packet */ return 2; /* bad packet */
GETSHORT(aqtype, p1); GETSHORT(aqtype, p1);
GETSHORT(aqclass, p1); GETSHORT(aqclass, p1);
@@ -651,7 +653,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
log_query(secflag | F_CNAME | F_FORWARD | F_UPSTREAM, name, NULL, NULL, 0); log_query(secflag | F_CNAME | F_FORWARD | F_UPSTREAM, name, NULL, NULL, 0);
if (!extract_name(header, qlen, &p1, name, 1, 0)) if (!extract_name(header, qlen, &p1, name, 1, 0))
return 0; return 2;
if (aqtype == T_CNAME) if (aqtype == T_CNAME)
{ {
@@ -677,7 +679,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
p1 = endrr; p1 = endrr;
if (!CHECK_LEN(header, p1, qlen, 0)) if (!CHECK_LEN(header, p1, qlen, 0))
return 0; /* bad packet */ return 2; /* bad packet */
} }
} }
@@ -722,14 +724,14 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
cname_loop1: cname_loop1:
if (!(p1 = skip_questions(header, qlen))) if (!(p1 = skip_questions(header, qlen)))
return 0; return 2;
for (j = 0; j < ntohs(header->ancount); j++) for (j = 0; j < ntohs(header->ancount); j++)
{ {
int secflag = 0; int secflag = 0;
if (!(res = extract_name(header, qlen, &p1, name, 0, 10))) if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
return 0; /* bad packet */ return 2; /* bad packet */
GETSHORT(aqtype, p1); GETSHORT(aqtype, p1);
GETSHORT(aqclass, p1); GETSHORT(aqclass, p1);
@@ -747,7 +749,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
{ {
p1 = endrr; p1 = endrr;
if (!CHECK_LEN(header, p1, qlen, 0)) if (!CHECK_LEN(header, p1, qlen, 0))
return 0; /* bad packet */ return 2; /* bad packet */
continue; continue;
} }
@@ -790,7 +792,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
namep = p1; namep = p1;
if (!extract_name(header, qlen, &p1, name, 1, 0)) if (!extract_name(header, qlen, &p1, name, 1, 0))
return 0; return 2;
if (qtype != T_CNAME) if (qtype != T_CNAME)
goto cname_loop1; goto cname_loop1;
@@ -813,25 +815,25 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
unsigned char *tmp = namep; unsigned char *tmp = namep;
if (!CHECK_LEN(header, p1, qlen, 6)) if (!CHECK_LEN(header, p1, qlen, 6))
return 0; /* bad packet */ return 2; /* bad packet */
GETSHORT(addr.srv.priority, p1); GETSHORT(addr.srv.priority, p1);
GETSHORT(addr.srv.weight, p1); GETSHORT(addr.srv.weight, p1);
GETSHORT(addr.srv.srvport, p1); GETSHORT(addr.srv.srvport, p1);
if (!extract_name(header, qlen, &p1, name, 1, 0)) if (!extract_name(header, qlen, &p1, name, 1, 0))
return 0; return 2;
addr.srv.targetlen = strlen(name) + 1; /* include terminating zero */ addr.srv.targetlen = strlen(name) + 1; /* include terminating zero */
if (!(addr.srv.target = blockdata_alloc(name, addr.srv.targetlen))) if (!(addr.srv.target = blockdata_alloc(name, addr.srv.targetlen)))
return 0; return 0;
/* we overwrote the original name, so get it back here. */ /* we overwrote the original name, so get it back here. */
if (!extract_name(header, qlen, &tmp, name, 1, 0)) if (!extract_name(header, qlen, &tmp, name, 1, 0))
return 0; return 2;
} }
else if (flags & (F_IPV4 | F_IPV6)) else if (flags & (F_IPV4 | F_IPV6))
{ {
/* copy address into aligned storage */ /* copy address into aligned storage */
if (!CHECK_LEN(header, p1, qlen, addrlen)) if (!CHECK_LEN(header, p1, qlen, addrlen))
return 0; /* bad packet */ return 2; /* bad packet */
memcpy(&addr, p1, addrlen); memcpy(&addr, p1, addrlen);
/* check for returned address in private space */ /* check for returned address in private space */
@@ -875,7 +877,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
if (aqtype == T_TXT) if (aqtype == T_TXT)
{ {
if (!print_txt(header, qlen, name, p1, ardlen, secflag)) if (!print_txt(header, qlen, name, p1, ardlen, secflag))
return 0; return 2;
} }
else else
log_query(flags | F_FORWARD | secflag | F_UPSTREAM, name, &addr, NULL, aqtype); log_query(flags | F_FORWARD | secflag | F_UPSTREAM, name, &addr, NULL, aqtype);
@@ -883,7 +885,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
p1 = endrr; p1 = endrr;
if (!CHECK_LEN(header, p1, qlen, 0)) if (!CHECK_LEN(header, p1, qlen, 0))
return 0; /* bad packet */ return 2; /* bad packet */
} }
if (!found && (qtype != T_ANY || (flags & F_NXDOMAIN))) if (!found && (qtype != T_ANY || (flags & F_NXDOMAIN)))