mirror of
https://github.com/pi-hole/dnsmasq.git
synced 2025-12-19 10:18:25 +00:00
Add --dnssec-limits option.
This commit is contained in:
@@ -931,6 +931,15 @@ Authenticated Data bit correctly in all cases is not technically possible. If th
|
||||
when using this option, then the cache should be disabled using --cache-size=0. In most cases, enabling DNSSEC validation
|
||||
within dnsmasq is a better option. See --dnssec for details.
|
||||
.TP
|
||||
.B --dnssec-limits=<limit>[,<limit>.......]
|
||||
Override the default resource limits applied to DNSSEC validation. Cryptographic operations are expensive and crafted domains
|
||||
can DoS a DNSSEC validator by forcing it to do hundreds of thousands of such operations. To avoid this, the dnsmasq validation code
|
||||
applies limits on how much work will be expended in validation. If any of the limits are exceeded, the validation will fail and the
|
||||
domain treated as BOGUS. There are four limits, in order(default values in parens): number a signature validation fails per RRset(20), number of signature validations and
|
||||
hash computations per query(200), number of sub-queries to fetch DS and DNSKEY RRsets per query(40), and the number of iterations in a NSEC3 record(150).
|
||||
The maximum values reached during validation are stored, and dumped as part of the stats generated by SIGUSR1. Supplying a limit value of 0 leaves the default in place, so
|
||||
\fB--dnssec-limits=0,0,20\fP sets the number of sub-queries to 20 whilst leaving the other limits at default values.
|
||||
.TP
|
||||
.B --dnssec-debug
|
||||
Set debugging mode for the DNSSEC validation, set the Checking Disabled bit on upstream queries,
|
||||
and don't convert replies which do not validate to responses with
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
#define EDNS_PKTSZ 1232 /* default max EDNS.0 UDP packet from from /dnsflagday.net/2020 */
|
||||
#define SAFE_PKTSZ 1232 /* "go anywhere" UDP packet size, see https://dnsflagday.net/2020/ */
|
||||
#define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */
|
||||
#define DNSSEC_WORK 50 /* Max number of queries to validate one question */
|
||||
#define LIMIT_SIG_FAIL 20 /* Number of signature that can fail to validate in one answer */
|
||||
#define LIMIT_CRYPTO 200 /* max no. of crypto operations to validate one query. */
|
||||
#define LIMIT_NSEC3_ITERS 150 /* Max. number if iterations allowed in NSEC3 record. */
|
||||
#define DNSSEC_LIMIT_WORK 40 /* Max number of queries to validate one question */
|
||||
#define DNSSEC_LIMIT_SIG_FAIL 20 /* Number of signature that can fail to validate in one answer */
|
||||
#define DNSSEC_LIMIT_CRYPTO 200 /* max no. of crypto operations to validate one query. */
|
||||
#define DNSSEC_LIMIT_NSEC3_ITERS 150 /* Max. number if iterations allowed in NSEC3 record. */
|
||||
#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
|
||||
#define SMALL_PORT_RANGE 30 /* If DNS port range is smaller than this, use different allocation. */
|
||||
#define FORWARD_TEST 50 /* try all servers every 50 queries */
|
||||
|
||||
@@ -834,6 +834,12 @@ struct frec {
|
||||
#define LEASE_HAVE_HWADDR 128 /* Have set hwaddress */
|
||||
#define LEASE_EXP_CHANGED 256 /* Lease expiry time changed */
|
||||
|
||||
#define LIMIT_SIG_FAIL 0
|
||||
#define LIMIT_CRYPTO 1
|
||||
#define LIMIT_WORK 2
|
||||
#define LIMIT_NSEC3_ITERS 3
|
||||
#define LIMIT_MAX 4
|
||||
|
||||
struct dhcp_lease {
|
||||
int clid_len; /* length of client identifier */
|
||||
unsigned char *clid; /* clientid */
|
||||
@@ -1243,7 +1249,7 @@ extern struct daemon {
|
||||
int rr_status_sz;
|
||||
int dnssec_no_time_check;
|
||||
int back_to_the_future;
|
||||
int limit_sig_fail, limit_crypto, limit_work, limit_nsec3_iters;
|
||||
int limit[LIMIT_MAX];
|
||||
#endif
|
||||
struct frec *frec_list;
|
||||
struct frec_src *free_frec_src;
|
||||
|
||||
@@ -479,7 +479,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname);
|
||||
|
||||
/* Now try all the sigs to try and find one which validates */
|
||||
for (sig_fail_cnt = daemon->limit_sig_fail, j = 0; j <sigidx; j++)
|
||||
for (sig_fail_cnt = daemon->limit[LIMIT_SIG_FAIL], j = 0; j <sigidx; j++)
|
||||
{
|
||||
unsigned char *psav, *sig, *digest;
|
||||
int i, wire_len, sig_len;
|
||||
@@ -692,8 +692,8 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
/* An attacker can waste a lot of our CPU by setting up a giant DNSKEY RRSET full of failing
|
||||
keys, all of which we have to try. Since many failing keys is not likely for
|
||||
a legitimate domain, set a limit on how many can fail. */
|
||||
if ((daemon->limit_sig_fail - (sig_fail_cnt + 1)) > (int)daemon->metrics[METRIC_SIG_FAIL_HWM])
|
||||
daemon->metrics[METRIC_SIG_FAIL_HWM] = daemon->limit_sig_fail - (sig_fail_cnt + 1);
|
||||
if ((daemon->limit[LIMIT_SIG_FAIL] - (sig_fail_cnt + 1)) > (int)daemon->metrics[METRIC_SIG_FAIL_HWM])
|
||||
daemon->metrics[METRIC_SIG_FAIL_HWM] = daemon->limit[LIMIT_SIG_FAIL] - (sig_fail_cnt + 1);
|
||||
if (dec_counter(&sig_fail_cnt, _("per-RRSet signature fails")))
|
||||
return STAT_ABANDONED;
|
||||
}
|
||||
@@ -1532,7 +1532,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
|
||||
|
||||
GETSHORT (iterations, p);
|
||||
/* Upper-bound iterations, to avoid DoS. RFC 9276 refers. */
|
||||
if (iterations > daemon->limit_nsec3_iters)
|
||||
if (iterations > daemon->limit[LIMIT_NSEC3_ITERS])
|
||||
return DNSSEC_FAIL_NSEC3_ITERS;
|
||||
|
||||
salt_len = *p++;
|
||||
|
||||
@@ -338,8 +338,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
if (ad_reqd)
|
||||
forward->flags |= FREC_AD_QUESTION;
|
||||
#ifdef HAVE_DNSSEC
|
||||
forward->work_counter = daemon->limit_work;
|
||||
forward->validate_counter = daemon->limit_crypto;
|
||||
forward->work_counter = daemon->limit[LIMIT_WORK];
|
||||
forward->validate_counter = daemon->limit[LIMIT_CRYPTO];
|
||||
if (do_bit)
|
||||
forward->flags |= FREC_DO_QUESTION;
|
||||
#endif
|
||||
@@ -1358,11 +1358,11 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he
|
||||
}
|
||||
}
|
||||
|
||||
if ((daemon->limit_crypto - forward->validate_counter) > (int)daemon->metrics[METRIC_CRYPTO_HWM])
|
||||
daemon->metrics[METRIC_CRYPTO_HWM] = daemon->limit_crypto - forward->validate_counter;
|
||||
if ((daemon->limit[LIMIT_CRYPTO] - forward->validate_counter) > (int)daemon->metrics[METRIC_CRYPTO_HWM])
|
||||
daemon->metrics[METRIC_CRYPTO_HWM] = daemon->limit[LIMIT_CRYPTO] - forward->validate_counter;
|
||||
|
||||
if ((daemon->limit_work - forward->work_counter) > (int)daemon->metrics[METRIC_WORK_HWM])
|
||||
daemon->metrics[METRIC_WORK_HWM] = daemon->limit_work - forward->work_counter;
|
||||
if ((daemon->limit[LIMIT_WORK] - forward->work_counter) > (int)daemon->metrics[METRIC_WORK_HWM])
|
||||
daemon->metrics[METRIC_WORK_HWM] = daemon->limit[LIMIT_WORK] - forward->work_counter;
|
||||
#endif
|
||||
|
||||
if (option_bool(OPT_NO_REBIND))
|
||||
@@ -2420,8 +2420,8 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
#ifdef HAVE_DNSSEC
|
||||
if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled && (master->flags & SERV_DO_DNSSEC))
|
||||
{
|
||||
int keycount = daemon->limit_work; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
|
||||
int validatecount = daemon->limit_crypto;
|
||||
int keycount = daemon->limit[LIMIT_WORK]; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
|
||||
int validatecount = daemon->limit[LIMIT_CRYPTO];
|
||||
int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname,
|
||||
serv, have_mark, mark, &keycount, &validatecount);
|
||||
char *result, *domain = "result";
|
||||
@@ -2450,11 +2450,11 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
|
||||
log_query(F_SECSTAT, domain, &a, result, 0);
|
||||
|
||||
if ((daemon->limit_crypto - validatecount) > (int)daemon->metrics[METRIC_CRYPTO_HWM])
|
||||
daemon->metrics[METRIC_CRYPTO_HWM] = daemon->limit_crypto - validatecount;
|
||||
if ((daemon->limit[LIMIT_CRYPTO] - validatecount) > (int)daemon->metrics[METRIC_CRYPTO_HWM])
|
||||
daemon->metrics[METRIC_CRYPTO_HWM] = daemon->limit[LIMIT_CRYPTO] - validatecount;
|
||||
|
||||
if ((daemon->limit_work - keycount) > (int)daemon->metrics[METRIC_WORK_HWM])
|
||||
daemon->metrics[METRIC_WORK_HWM] = daemon->limit_work - keycount;
|
||||
if ((daemon->limit[LIMIT_WORK] - keycount) > (int)daemon->metrics[METRIC_WORK_HWM])
|
||||
daemon->metrics[METRIC_WORK_HWM] = daemon->limit[LIMIT_WORK] - keycount;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
29
src/option.c
29
src/option.c
@@ -191,6 +191,7 @@ struct myoption {
|
||||
#define LOPT_NO_DHCP6 382
|
||||
#define LOPT_NO_DHCP4 383
|
||||
#define LOPT_MAX_PROCS 384
|
||||
#define LOPT_DNSSEC_LIMITS 385
|
||||
|
||||
#ifdef HAVE_GETOPT_LONG
|
||||
static const struct option opts[] =
|
||||
@@ -364,6 +365,7 @@ static const struct myoption opts[] =
|
||||
{ "dnssec-check-unsigned", 2, 0, LOPT_DNSSEC_CHECK },
|
||||
{ "dnssec-no-timecheck", 0, 0, LOPT_DNSSEC_TIME },
|
||||
{ "dnssec-timestamp", 1, 0, LOPT_DNSSEC_STAMP },
|
||||
{ "dnssec-limits", 1, 0, LOPT_DNSSEC_LIMITS },
|
||||
{ "dhcp-relay", 1, 0, LOPT_RELAY },
|
||||
{ "ra-param", 1, 0, LOPT_RA_PARAM },
|
||||
{ "quiet-dhcp", 0, 0, LOPT_QUIET_DHCP },
|
||||
@@ -568,6 +570,7 @@ static struct {
|
||||
{ LOPT_DNSSEC_CHECK, ARG_DUP, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL },
|
||||
{ LOPT_DNSSEC_TIME, OPT_DNSSEC_TIME, NULL, gettext_noop("Don't check DNSSEC signature timestamps until first cache-reload"), NULL },
|
||||
{ LOPT_DNSSEC_STAMP, ARG_ONE, "<path>", gettext_noop("Timestamp file to verify system clock for DNSSEC"), NULL },
|
||||
{ LOPT_DNSSEC_LIMITS, ARG_ONE, "<limit>,..", gettext_noop("Set resource limits for DNSSEC validation"), NULL },
|
||||
{ LOPT_RA_PARAM, ARG_DUP, "<iface>,[mtu:<value>|<interface>|off,][<prio>,]<intval>[,<lifetime>]", gettext_noop("Set MTU, priority, resend-interval and router-lifetime"), NULL },
|
||||
{ LOPT_QUIET_DHCP, OPT_QUIET_DHCP, NULL, gettext_noop("Do not log routine DHCP."), NULL },
|
||||
{ LOPT_QUIET_DHCP6, OPT_QUIET_DHCP6, NULL, gettext_noop("Do not log routine DHCPv6."), NULL },
|
||||
@@ -5258,6 +5261,24 @@ err:
|
||||
}
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
case LOPT_DNSSEC_LIMITS:
|
||||
{
|
||||
int lim, val;
|
||||
|
||||
for (lim = LIMIT_SIG_FAIL; arg && lim < LIMIT_MAX ; lim++, arg = comma)
|
||||
{
|
||||
comma = split(arg);
|
||||
|
||||
if (!atoi_check(arg, &val))
|
||||
ret_err(gen_err);
|
||||
|
||||
if (val != 0)
|
||||
daemon->limit[lim] = val;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case LOPT_DNSSEC_STAMP: /* --dnssec-timestamp */
|
||||
daemon->timestamp_file = opt_string_alloc(arg);
|
||||
break;
|
||||
@@ -5870,10 +5891,10 @@ void read_opts(int argc, char **argv, char *compile_opts)
|
||||
daemon->host_index = SRC_AH;
|
||||
daemon->max_procs = MAX_PROCS;
|
||||
#ifdef HAVE_DNSSEC
|
||||
daemon->limit_sig_fail = LIMIT_SIG_FAIL;
|
||||
daemon->limit_crypto = LIMIT_CRYPTO;
|
||||
daemon->limit_work = DNSSEC_WORK;
|
||||
daemon->limit_nsec3_iters = LIMIT_NSEC3_ITERS;
|
||||
daemon->limit[LIMIT_SIG_FAIL] = DNSSEC_LIMIT_SIG_FAIL;
|
||||
daemon->limit[LIMIT_CRYPTO] = DNSSEC_LIMIT_CRYPTO;
|
||||
daemon->limit[LIMIT_WORK] = DNSSEC_LIMIT_WORK;
|
||||
daemon->limit[LIMIT_NSEC3_ITERS] = DNSSEC_LIMIT_NSEC3_ITERS;
|
||||
#endif
|
||||
|
||||
/* See comment above make_servers(). Optimises server-read code. */
|
||||
|
||||
Reference in New Issue
Block a user