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 82 additions and 33 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.
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
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
that they are received from upstream.
.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
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
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
.B \-0, --dns-forward-max=<queries>
Set the maximum number of concurrent DNS queries. The default value is
+15 -5
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)
{
/* Don't dump expired entries if we're using them, cache becomes strictly LRU in that case.
Never use expired DS or DNSKEY entries. */
if (option_bool(OPT_STALE_CACHE) && !(crecp->flags & (F_DS | F_DNSKEY)))
/* Don't dump expired entries if they are within the accepted timeout range.
The cache becomes approx. LRU. Never use expired DS or DNSKEY entries.
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;
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]);
my_syslog(LOG_INFO, _("queries forwarded %u, queries answered locally %u"),
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]);
#ifdef HAVE_AUTH
my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->metrics[METRIC_DNS_AUTH_ANSWERED]);
@@ -1889,8 +1897,10 @@ void dump_cache(time_t now)
char *record_source(unsigned int index)
{
struct hostsfile *ah;
#ifdef HAVE_INOTIFY
struct dyndir *dd;
#endif
if (index == SRC_CONFIG)
return "config";
else if (index == SRC_HOSTS)
+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_TYPE T_TXT
#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.
make COPTS=-DHAVE_BROKEN_RTC
+2
View File
@@ -722,6 +722,8 @@ static const struct opttab_t opttab6[] = {
{ "sntp-server", 31, OT_ADDR_LIST },
{ "information-refresh-time", 32, OT_TIME },
{ "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 */ },
{ "bootfile-url", 59, OT_NAME },
{ "bootfile-param", 60, OT_CSTRING },
+3 -3
View File
@@ -280,9 +280,8 @@ struct event_desc {
#define OPT_FILTER_AAAA 68
#define OPT_STRIP_ECS 69
#define OPT_STRIP_MAC 70
#define OPT_STALE_CACHE 71
#define OPT_NORR 72
#define OPT_LAST 73
#define OPT_NORR 71
#define OPT_LAST 72
#define OPTION_BITS (sizeof(unsigned int)*8)
#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;
u32 metrics[__METRIC_MAX];
int fast_retry_time, fast_retry_timeout;
int cache_max_expiry;
#ifdef HAVE_DNSSEC
struct ds_config *ds;
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++;
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.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 */
void mark_servers(int flag)
{
struct server *serv, **up;
struct server *serv, *next, **up;
maybe_free_servers = !!flag;
@@ -580,11 +580,13 @@ void mark_servers(int flag)
1) numerous and 2) not reloaded often. We just delete
and recreate. */
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)
{
*up = serv->next;
*up = next;
free(serv->domain);
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);
}
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);
munged = 1;
cache_secure = 0;
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)
+20 -2
View File
@@ -373,7 +373,7 @@ static const struct myoption opts[] =
{ "quiet-tftp", 0, 0, LOPT_QUIET_TFTP },
{ "port-limit", 1, 0, LOPT_RANDPORT_LIM },
{ "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 }
};
@@ -431,7 +431,7 @@ static struct {
{ '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_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', 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},
@@ -5151,6 +5151,24 @@ err:
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
case LOPT_DNSSEC_STAMP: /* --dnssec-timestamp */
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,
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.
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,
struct ipsets *ipsets, struct ipsets *nftsets, int is_sign, int check_rebind,
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);
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(qclass, p);
@@ -607,13 +609,13 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
{
cname_loop:
if (!(p1 = skip_questions(header, qlen)))
return 0;
return 2;
for (j = 0; j < ntohs(header->ancount); j++)
{
int secflag = 0;
if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
return 0; /* bad packet */
return 2; /* bad packet */
GETSHORT(aqtype, 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);
if (!extract_name(header, qlen, &p1, name, 1, 0))
return 0;
return 2;
if (aqtype == T_CNAME)
{
@@ -677,7 +679,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
p1 = endrr;
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:
if (!(p1 = skip_questions(header, qlen)))
return 0;
return 2;
for (j = 0; j < ntohs(header->ancount); j++)
{
int secflag = 0;
if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
return 0; /* bad packet */
return 2; /* bad packet */
GETSHORT(aqtype, p1);
GETSHORT(aqclass, p1);
@@ -747,7 +749,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
{
p1 = endrr;
if (!CHECK_LEN(header, p1, qlen, 0))
return 0; /* bad packet */
return 2; /* bad packet */
continue;
}
@@ -790,7 +792,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
namep = p1;
if (!extract_name(header, qlen, &p1, name, 1, 0))
return 0;
return 2;
if (qtype != T_CNAME)
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;
if (!CHECK_LEN(header, p1, qlen, 6))
return 0; /* bad packet */
return 2; /* bad packet */
GETSHORT(addr.srv.priority, p1);
GETSHORT(addr.srv.weight, p1);
GETSHORT(addr.srv.srvport, p1);
if (!extract_name(header, qlen, &p1, name, 1, 0))
return 0;
return 2;
addr.srv.targetlen = strlen(name) + 1; /* include terminating zero */
if (!(addr.srv.target = blockdata_alloc(name, addr.srv.targetlen)))
return 0;
/* we overwrote the original name, so get it back here. */
if (!extract_name(header, qlen, &tmp, name, 1, 0))
return 0;
return 2;
}
else if (flags & (F_IPV4 | F_IPV6))
{
/* copy address into aligned storage */
if (!CHECK_LEN(header, p1, qlen, addrlen))
return 0; /* bad packet */
return 2; /* bad packet */
memcpy(&addr, p1, addrlen);
/* 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 (!print_txt(header, qlen, name, p1, ardlen, secflag))
return 0;
return 2;
}
else
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;
if (!CHECK_LEN(header, p1, qlen, 0))
return 0; /* bad packet */
return 2; /* bad packet */
}
if (!found && (qtype != T_ANY || (flags & F_NXDOMAIN)))