diff --git a/CHANGES b/CHANGES index debb9c1b91..8d405c1fc8 100644 --- a/CHANGES +++ b/CHANGES @@ -20,8 +20,22 @@ not negotiate "dot" ALPN token could crash BIND on shutdown. That has been fixed. [GL #3767] -6063. [bug] Revert a change that limited to honour single - read for TLSDNS as it broke XoT. [GL #3772] + --- 9.18.11 released --- + +6067. [security] Fix serve-stale crash when recursive clients soft quota + is reached. (CVE-2022-3924) [GL #3619] + +6066. [security] Handle RRSIG lookups when serve-stale is active. + (CVE-2022-3736) [GL #3622] + +6064. [security] An UPDATE message flood could cause named to exhaust all + available memory. This flaw was addressed by adding a + new "update-quota" statement that controls the number of + simultaneous UPDATE messages that can be processed or + forwarded. The default is 100. A stats counter has been + added to record events when the update quota is + exceeded, and the XML and JSON statistics version + numbers have been updated. (CVE-2022-3094) [GL #3523] 6062. [func] The DSCP implementation, which has been nonfunctional for some time, is now marked as @@ -88,7 +102,9 @@ [GL #3252] 5830. [func] Implement incremental resizing of isc_ht hash tables to - perform the rehashing gradually. [GL #3212] + perform the rehashing gradually. The catalog zone + implementation has been optimized to work with hundreds + of thousands of member zones. [GL #3212] [GL #3744] --- 9.18.10 released --- diff --git a/bin/named/bind9.xsl b/bin/named/bind9.xsl index 27161860d0..9dda61deea 100644 --- a/bin/named/bind9.xsl +++ b/bin/named/bind9.xsl @@ -15,7 +15,7 @@ - + diff --git a/bin/named/config.c b/bin/named/config.c index 86bade216e..b2b802806b 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -137,6 +137,7 @@ options {\n\ trust-anchor-telemetry yes;\n\ udp-receive-buffer 0;\n\ udp-send-buffer 0;\n\ + update-quota 100;\n\ \n\ /* view */\n\ allow-new-zones no;\n\ diff --git a/bin/named/server.c b/bin/named/server.c index fd6b11e7a4..1d7478501d 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -8686,6 +8686,7 @@ load_configuration(const char *filename, named_server_t *server, configure_server_quota(maps, "tcp-clients", &server->sctx->tcpquota); configure_server_quota(maps, "recursive-clients", &server->sctx->recursionquota); + configure_server_quota(maps, "update-quota", &server->sctx->updquota); max = isc_quota_getmax(&server->sctx->recursionquota); if (max > 1000) { diff --git a/bin/named/statschannel.c b/bin/named/statschannel.c index cd56029245..f6ce425445 100644 --- a/bin/named/statschannel.c +++ b/bin/named/statschannel.c @@ -55,11 +55,11 @@ #include "xsl_p.h" #define STATS_XML_VERSION_MAJOR "3" -#define STATS_XML_VERSION_MINOR "12" +#define STATS_XML_VERSION_MINOR "13" #define STATS_XML_VERSION STATS_XML_VERSION_MAJOR "." STATS_XML_VERSION_MINOR #define STATS_JSON_VERSION_MAJOR "1" -#define STATS_JSON_VERSION_MINOR "6" +#define STATS_JSON_VERSION_MINOR "7" #define STATS_JSON_VERSION STATS_JSON_VERSION_MAJOR "." STATS_JSON_VERSION_MINOR #define CHECK(m) \ @@ -351,6 +351,7 @@ init_desc(void) { SET_NSSTATDESC(reclimitdropped, "queries dropped due to recursive client limit", "RecLimitDropped"); + SET_NSSTATDESC(updatequota, "Update quota exceeded", "UpdateQuota"); INSIST(i == ns_statscounter_max); diff --git a/bin/tests/system/checkconf/good.conf b/bin/tests/system/checkconf/good.conf index 93939ff3c8..f8d04089f0 100644 --- a/bin/tests/system/checkconf/good.conf +++ b/bin/tests/system/checkconf/good.conf @@ -72,6 +72,7 @@ options { recursive-clients 3000; serial-query-rate 100; server-id none; + update-quota 200; check-names primary warn; check-names secondary ignore; max-cache-size 20000000000000; diff --git a/bin/tests/system/nsupdate/ns1/named.conf.in b/bin/tests/system/nsupdate/ns1/named.conf.in index fef5beb1f0..b502ea7a1e 100644 --- a/bin/tests/system/nsupdate/ns1/named.conf.in +++ b/bin/tests/system/nsupdate/ns1/named.conf.in @@ -23,6 +23,7 @@ options { recursion no; notify yes; minimal-responses no; + update-quota 1; }; acl named-acl { @@ -83,6 +84,7 @@ zone "other.nil" { check-integrity no; check-mx warn; update-policy local; + allow-query { !10.53.0.2; any; }; allow-query-on { 10.53.0.1; 127.0.0.1; }; allow-transfer { any; }; }; diff --git a/bin/tests/system/nsupdate/tests.sh b/bin/tests/system/nsupdate/tests.sh index d612a226d0..9b80dd464c 100755 --- a/bin/tests/system/nsupdate/tests.sh +++ b/bin/tests/system/nsupdate/tests.sh @@ -1353,6 +1353,34 @@ grep '10.53.0.1.*REFUSED' nsupdate.out-$n > /dev/null || ret=1 grep 'Reply from SOA query' nsupdate.out-$n > /dev/null || ret=1 [ $ret = 0 ] || { echo_i "failed"; status=1; } +n=$((n + 1)) +ret=0 +echo_i "check that update is rejected if query is not allowed ($n)" +{ + $NSUPDATE -d < nsupdate.out.test$n 2>&1 +grep 'failed: REFUSED' nsupdate.out.test$n > /dev/null || ret=1 +[ $ret = 0 ] || { echo_i "failed"; status=1; } + +n=$((n + 1)) +ret=0 +echo_i "check that update is rejected if quota is exceeded ($n)" +for loop in 1 2 3 4 5 6 7 8 9 10; do +{ + $NSUPDATE -4 -l -p ${PORT} -k ns1/session.key > /dev/null 2>&1 < nsupdate.out.$n 2>&1 +grep REFUSED nsupdate.out.$n > /dev/null || ret=1 +if [ $ret != 0 ] ; then echo_i "failed"; status=`expr $status + $ret`; fi +n=`expr $n + 1` + +n=$((n + 1)) +ret=0 +echo_i "attempting updates that should exceed quota ($n)" +# lower the update quota to 1. +copy_setports ns3/named2.conf.in ns3/named.conf +rndc_reconfig ns3 10.53.0.3 +nextpart ns3/named.run > /dev/null +for loop in 1 2 3 4 5 6 7 8 9 10; do +{ + $NSUPDATE -- - > /dev/null 2>&1 <; udp\-send\-buffer ; update\-check\-ksk ; + update\-quota ; use\-alt\-transfer\-source ; // deprecated use\-v4\-udp\-ports { ; ... }; use\-v6\-udp\-ports { ; ... }; diff --git a/doc/misc/options b/doc/misc/options index a15ba67a69..7ca815617f 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -310,6 +310,7 @@ options { udp-receive-buffer ; udp-send-buffer ; update-check-ksk ; + update-quota ; use-alt-transfer-source ; // deprecated use-v4-udp-ports { ; ... }; use-v6-udp-ports { ; ... }; diff --git a/doc/notes/notes-9.18.11.rst b/doc/notes/notes-9.18.11.rst new file mode 100644 index 0000000000..3e44dc2d69 --- /dev/null +++ b/doc/notes/notes-9.18.11.rst @@ -0,0 +1,112 @@ +.. Copyright (C) Internet Systems Consortium, Inc. ("ISC") +.. +.. SPDX-License-Identifier: MPL-2.0 +.. +.. This Source Code Form is subject to the terms of the Mozilla Public +.. License, v. 2.0. If a copy of the MPL was not distributed with this +.. file, you can obtain one at https://mozilla.org/MPL/2.0/. +.. +.. See the COPYRIGHT file distributed with this work for additional +.. information regarding copyright ownership. + +Notes for BIND 9.18.11 +---------------------- + +Security Fixes +~~~~~~~~~~~~~~ + +- An UPDATE message flood could cause :iscman:`named` to exhaust all + available memory. This flaw was addressed by adding a new + :any:`update-quota` option that controls the maximum number of + outstanding DNS UPDATE messages that :iscman:`named` can hold in a + queue at any given time (default: 100). (CVE-2022-3094) + + ISC would like to thank Rob Schulhof from Infoblox for bringing this + vulnerability to our attention. :gl:`#3523` + +- :iscman:`named` could crash with an assertion failure when an RRSIG + query was received and :any:`stale-answer-client-timeout` was set to a + non-zero value. This has been fixed. (CVE-2022-3736) + + ISC would like to thank Borja Marcos from Sarenet (with assistance by + Iratxe Niño from Fundación Sarenet) for bringing this vulnerability to + our attention. :gl:`#3622` + +- :iscman:`named` running as a resolver with the + :any:`stale-answer-client-timeout` option set to any value greater + than ``0`` could crash with an assertion failure, when the + :any:`recursive-clients` soft quota was reached. This has been fixed. + (CVE-2022-3924) + + ISC would like to thank Maksym Odinintsev from AWS for bringing this + vulnerability to our attention. :gl:`#3619` + +New Features +~~~~~~~~~~~~ + +- The new :any:`update-quota` option can be used to control the number + of simultaneous DNS UPDATE messages that can be processed to update an + authoritative zone on a primary server, or forwarded to the primary + server by a secondary server. The default is 100. A new statistics + counter has also been added to record events when this quota is + exceeded, and the version numbers for the XML and JSON statistics + schemas have been updated. :gl:`#3523` + +Removed Features +~~~~~~~~~~~~~~~~ + +- The Differentiated Services Code Point (DSCP) feature in BIND has been + non-operational since the new Network Manager was introduced in BIND + 9.16. It is now marked as obsolete, and vestigial code implementing it + has been removed. Configuring DSCP values in ``named.conf`` now causes + a warning to be logged. :gl:`#3773` + +Feature Changes +~~~~~~~~~~~~~~~ + +- The catalog zone implementation has been optimized to work with + hundreds of thousands of member zones. :gl:`#3212` :gl:`#3744` + +Bug Fixes +~~~~~~~~~ + +- A rare assertion failure was fixed in outgoing TCP DNS connection + handling. :gl:`#3178` :gl:`#3636` + +- Large zone transfers over TLS (XoT) could fail. This has been fixed. + :gl:`#3772` + +- In addition to a previously fixed bug, another similar issue was + discovered where quotas could be erroneously reached for servers, + including any configured forwarders, resulting in SERVFAIL answers + being sent to clients. This has been fixed. :gl:`#3752` + +- In certain query resolution scenarios (e.g. when following CNAME + records), :iscman:`named` configured to answer from stale cache could + return a SERVFAIL response despite a usable, non-stale answer being + present in the cache. This has been fixed. :gl:`#3678` + +- When an outgoing request timed out, :iscman:`named` would retry up to + three times with the same server instead of trying the next available + name server. This has been fixed. :gl:`#3637` + +- Recently used ADB names and ADB entries (IP addresses) could get + cleaned when ADB was under memory pressure. To mitigate this, only + actual ADB names and ADB entries are now counted (excluding internal + memory structures used for "housekeeping") and recently used (<= 10 + seconds) ADB names and entries are excluded from the overmem memory + cleaner. :gl:`#3739` + +- The "Prohibited" Extended DNS Error was inadvertently set in some + NOERROR responses. This has been fixed. :gl:`#3743` + +- Previously, TLS session resumption could have led to handshake + failures when client certificates were used for authentication (Mutual + TLS). This has been fixed. :gl:`#3725` + +Known Issues +~~~~~~~~~~~~ + +- There are no new known issues with this release. See :ref:`above + ` for a list of all known issues affecting this + BIND 9 branch. diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 2cf8a22210..775ac72478 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -10976,6 +10976,8 @@ dns_resolver_cancelfetch(dns_fetch_t *fetch) { fetchctx_t *fctx = NULL; dns_resolver_t *res = NULL; dns_fetchevent_t *event = NULL; + dns_fetchevent_t *event_trystale = NULL; + dns_fetchevent_t *event_fetchdone = NULL; REQUIRE(DNS_FETCH_VALID(fetch)); fctx = fetch->private; @@ -10987,9 +10989,9 @@ dns_resolver_cancelfetch(dns_fetch_t *fetch) { LOCK(&res->buckets[fctx->bucketnum].lock); /* - * Find the completion event for this fetch (as opposed + * Find the events for this fetch (as opposed * to those for other fetches that have joined the same - * fctx) and send it with result = ISC_R_CANCELED. + * fctx) and send them with result = ISC_R_CANCELED. */ if (fctx->state != fetchstate_done) { dns_fetchevent_t *next_event = NULL; @@ -10999,15 +11001,42 @@ dns_resolver_cancelfetch(dns_fetch_t *fetch) { next_event = ISC_LIST_NEXT(event, ev_link); if (event->fetch == fetch) { ISC_LIST_UNLINK(fctx->events, event, ev_link); - break; + switch (event->ev_type) { + case DNS_EVENT_TRYSTALE: + INSIST(event_trystale == NULL); + event_trystale = event; + break; + case DNS_EVENT_FETCHDONE: + INSIST(event_fetchdone == NULL); + event_fetchdone = event; + break; + default: + UNREACHABLE(); + } + if (event_trystale != NULL && + event_fetchdone != NULL) + { + break; + } } } } - if (event != NULL) { - isc_task_t *etask = event->ev_sender; - event->ev_sender = fctx; - event->result = ISC_R_CANCELED; - isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event)); + /* + * The "trystale" event must be sent before the "fetchdone" event, + * because the latter clears the "recursing" query attribute, which is + * required by both events (handled by the same callback function). + */ + if (event_trystale != NULL) { + isc_task_t *etask = event_trystale->ev_sender; + event_trystale->ev_sender = fctx; + event_trystale->result = ISC_R_CANCELED; + isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event_trystale)); + } + if (event_fetchdone != NULL) { + isc_task_t *etask = event_fetchdone->ev_sender; + event_fetchdone->ev_sender = fctx; + event_fetchdone->result = ISC_R_CANCELED; + isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event_fetchdone)); } /* diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 5409a4ac49..9b37671230 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -1342,6 +1342,7 @@ static cfg_clausedef_t options_clauses[] = { { "treat-cr-as-space", NULL, CFG_CLAUSEFLAG_ANCIENT }, { "udp-receive-buffer", &cfg_type_uint32, 0 }, { "udp-send-buffer", &cfg_type_uint32, 0 }, + { "update-quota", &cfg_type_uint32, 0 }, { "use-id-pool", NULL, CFG_CLAUSEFLAG_ANCIENT }, { "use-ixfr", NULL, CFG_CLAUSEFLAG_ANCIENT }, { "use-v4-udp-ports", &cfg_type_bracketed_portlist, 0 }, diff --git a/lib/ns/include/ns/server.h b/lib/ns/include/ns/server.h index 0f11612d1e..4393e9798c 100644 --- a/lib/ns/include/ns/server.h +++ b/lib/ns/include/ns/server.h @@ -84,6 +84,7 @@ struct ns_server { isc_quota_t recursionquota; isc_quota_t tcpquota; isc_quota_t xfroutquota; + isc_quota_t updquota; ISC_LIST(isc_quota_t) http_quotas; isc_mutex_t http_quotas_lock; diff --git a/lib/ns/include/ns/stats.h b/lib/ns/include/ns/stats.h index 2de556423b..c235384f48 100644 --- a/lib/ns/include/ns/stats.h +++ b/lib/ns/include/ns/stats.h @@ -107,7 +107,9 @@ enum { ns_statscounter_reclimitdropped = 66, - ns_statscounter_max = 67, + ns_statscounter_updatequota = 67, + + ns_statscounter_max = 68, }; void diff --git a/lib/ns/query.c b/lib/ns/query.c index be15f36180..922160038c 100644 --- a/lib/ns/query.c +++ b/lib/ns/query.c @@ -5237,6 +5237,15 @@ qctx_init(ns_client_t *client, dns_fetchevent_t **eventp, dns_rdatatype_t qtype, qctx->result = ISC_R_SUCCESS; qctx->findcoveringnsec = qctx->view->synthfromdnssec; + /* + * If it's an RRSIG or SIG query, we'll iterate the node. + */ + if (qctx->qtype == dns_rdatatype_rrsig || + qctx->qtype == dns_rdatatype_sig) + { + qctx->type = dns_rdatatype_any; + } + CALL_HOOK_NORETURN(NS_QUERY_QCTX_INITIALIZED, qctx); } @@ -5424,15 +5433,6 @@ query_setup(ns_client_t *client, dns_rdatatype_t qtype) { CALL_HOOK(NS_QUERY_SETUP, &qctx); - /* - * If it's a SIG query, we'll iterate the node. - */ - if (qctx.qtype == dns_rdatatype_rrsig || - qctx.qtype == dns_rdatatype_sig) - { - qctx.type = dns_rdatatype_any; - } - /* * Check SERVFAIL cache */ @@ -6236,7 +6236,9 @@ fetch_callback(isc_task_t *task, isc_event_t *event) { CTRACE(ISC_LOG_DEBUG(3), "fetch_callback"); if (event->ev_type == DNS_EVENT_TRYSTALE) { - query_lookup_stale(client); + if (devent->result != ISC_R_CANCELED) { + query_lookup_stale(client); + } isc_event_free(ISC_EVENT_PTR(&event)); return; } diff --git a/lib/ns/server.c b/lib/ns/server.c index 7baf2efc37..ce46aad2be 100644 --- a/lib/ns/server.c +++ b/lib/ns/server.c @@ -54,6 +54,7 @@ ns_server_create(isc_mem_t *mctx, ns_matchview_t matchingview, isc_quota_init(&sctx->xfroutquota, 10); isc_quota_init(&sctx->tcpquota, 10); isc_quota_init(&sctx->recursionquota, 100); + isc_quota_init(&sctx->updquota, 100); ISC_LIST_INIT(sctx->http_quotas); isc_mutex_init(&sctx->http_quotas_lock); @@ -136,6 +137,7 @@ ns_server_detach(ns_server_t **sctxp) { isc_mem_put(sctx->mctx, altsecret, sizeof(*altsecret)); } + isc_quota_destroy(&sctx->updquota); isc_quota_destroy(&sctx->recursionquota); isc_quota_destroy(&sctx->tcpquota); isc_quota_destroy(&sctx->xfroutquota); diff --git a/lib/ns/update.c b/lib/ns/update.c index 186eef1d1d..46e74df8ea 100644 --- a/lib/ns/update.c +++ b/lib/ns/update.c @@ -231,6 +231,8 @@ struct update_event { dns_zone_t *zone; isc_result_t result; dns_message_t *answer; + const dns_ssurule_t **rules; + size_t ruleslen; }; /*% @@ -270,6 +272,9 @@ static void forward_done(isc_task_t *task, isc_event_t *event); static isc_result_t add_rr_prepare_action(void *data, rr_t *rr); +static isc_result_t +rr_exists(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, + const dns_rdata_t *rdata, bool *flag); /**************************************************************************/ @@ -342,25 +347,26 @@ inc_stats(ns_client_t *client, dns_zone_t *zone, isc_statscounter_t counter) { static isc_result_t checkqueryacl(ns_client_t *client, dns_acl_t *queryacl, dns_name_t *zonename, dns_acl_t *updateacl, dns_ssutable_t *ssutable) { + isc_result_t result; char namebuf[DNS_NAME_FORMATSIZE]; char classbuf[DNS_RDATACLASS_FORMATSIZE]; - int level; - isc_result_t result; + bool update_possible = + ((updateacl != NULL && !dns_acl_isnone(updateacl)) || + ssutable != NULL); result = ns_client_checkaclsilent(client, NULL, queryacl, true); if (result != ISC_R_SUCCESS) { + int level = update_possible ? ISC_LOG_ERROR : ISC_LOG_INFO; + dns_name_format(zonename, namebuf, sizeof(namebuf)); dns_rdataclass_format(client->view->rdclass, classbuf, sizeof(classbuf)); - level = (updateacl == NULL && ssutable == NULL) ? ISC_LOG_INFO - : ISC_LOG_ERROR; - ns_client_log(client, NS_LOGCATEGORY_UPDATE_SECURITY, NS_LOGMODULE_UPDATE, level, "update '%s/%s' denied due to allow-query", namebuf, classbuf); - } else if (updateacl == NULL && ssutable == NULL) { + } else if (!update_possible) { dns_name_format(zonename, namebuf, sizeof(namebuf)); dns_rdataclass_format(client->view->rdclass, classbuf, sizeof(classbuf)); @@ -1644,12 +1650,247 @@ send_update_event(ns_client_t *client, dns_zone_t *zone) { isc_result_t result = ISC_R_SUCCESS; update_event_t *event = NULL; isc_task_t *zonetask = NULL; + dns_ssutable_t *ssutable = NULL; + dns_message_t *request = client->message; + isc_mem_t *mctx = client->manager->mctx; + dns_aclenv_t *env = client->manager->aclenv; + dns_rdataclass_t zoneclass; + dns_rdatatype_t covers; + dns_name_t *zonename = NULL; + const dns_ssurule_t **rules = NULL; + size_t rule = 0, ruleslen = 0; + dns_db_t *db = NULL; + dns_dbversion_t *ver = NULL; + + CHECK(dns_zone_getdb(zone, &db)); + zonename = dns_db_origin(db); + zoneclass = dns_db_class(db); + dns_zone_getssutable(zone, &ssutable); + dns_db_currentversion(db, &ver); + + /* + * Update message processing can leak record existence information + * so check that we are allowed to query this zone. Additionally, + * if we would refuse all updates for this zone, we bail out here. + */ + CHECK(checkqueryacl(client, dns_zone_getqueryacl(zone), + dns_zone_getorigin(zone), + dns_zone_getupdateacl(zone), ssutable)); + + /* + * Check requestor's permissions. + */ + if (ssutable == NULL) { + CHECK(checkupdateacl(client, dns_zone_getupdateacl(zone), + "update", dns_zone_getorigin(zone), false, + false)); + } else if (client->signer == NULL && !TCPCLIENT(client)) { + CHECK(checkupdateacl(client, NULL, "update", + dns_zone_getorigin(zone), false, true)); + } + + if (dns_zone_getupdatedisabled(zone)) { + FAILC(DNS_R_REFUSED, "dynamic update temporarily disabled " + "because the zone is frozen. Use " + "'rndc thaw' to re-enable updates."); + } + + /* + * Prescan the update section, checking for updates that + * are illegal or violate policy. + */ + if (ssutable != NULL) { + ruleslen = request->counts[DNS_SECTION_UPDATE]; + rules = isc_mem_get(mctx, sizeof(*rules) * ruleslen); + memset(rules, 0, sizeof(*rules) * ruleslen); + } + + for (rule = 0, + result = dns_message_firstname(request, DNS_SECTION_UPDATE); + result == ISC_R_SUCCESS; + rule++, result = dns_message_nextname(request, DNS_SECTION_UPDATE)) + { + dns_name_t *name = NULL; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_ttl_t ttl; + dns_rdataclass_t update_class; + + INSIST(ssutable == NULL || rule < ruleslen); + + get_current_rr(request, DNS_SECTION_UPDATE, zoneclass, &name, + &rdata, &covers, &ttl, &update_class); + + if (!dns_name_issubdomain(name, zonename)) { + FAILC(DNS_R_NOTZONE, "update RR is outside zone"); + } + if (update_class == zoneclass) { + /* + * Check for meta-RRs. The RFC2136 pseudocode says + * check for ANY|AXFR|MAILA|MAILB, but the text adds + * "or any other QUERY metatype" + */ + if (dns_rdatatype_ismeta(rdata.type)) { + FAILC(DNS_R_FORMERR, "meta-RR in update"); + } + result = dns_zone_checknames(zone, name, &rdata); + if (result != ISC_R_SUCCESS) { + FAIL(DNS_R_REFUSED); + } + } else if (update_class == dns_rdataclass_any) { + if (ttl != 0 || rdata.length != 0 || + (dns_rdatatype_ismeta(rdata.type) && + rdata.type != dns_rdatatype_any)) + { + FAILC(DNS_R_FORMERR, "meta-RR in update"); + } + } else if (update_class == dns_rdataclass_none) { + if (ttl != 0 || dns_rdatatype_ismeta(rdata.type)) { + FAILC(DNS_R_FORMERR, "meta-RR in update"); + } + } else { + update_log(client, zone, ISC_LOG_WARNING, + "update RR has incorrect class %d", + update_class); + FAIL(DNS_R_FORMERR); + } + + /* + * draft-ietf-dnsind-simple-secure-update-01 says + * "Unlike traditional dynamic update, the client + * is forbidden from updating NSEC records." + */ + if (rdata.type == dns_rdatatype_nsec3) { + FAILC(DNS_R_REFUSED, "explicit NSEC3 updates are not " + "allowed " + "in secure zones"); + } else if (rdata.type == dns_rdatatype_nsec) { + FAILC(DNS_R_REFUSED, "explicit NSEC updates are not " + "allowed " + "in secure zones"); + } else if (rdata.type == dns_rdatatype_rrsig && + !dns_name_equal(name, zonename)) + { + FAILC(DNS_R_REFUSED, "explicit RRSIG updates are " + "currently " + "not supported in secure zones " + "except " + "at the apex"); + } + + if (ssutable != NULL) { + isc_netaddr_t netaddr; + dns_name_t *target = NULL; + dst_key_t *tsigkey = NULL; + dns_rdata_ptr_t ptr; + dns_rdata_in_srv_t srv; + + isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr); + + if (client->message->tsigkey != NULL) { + tsigkey = client->message->tsigkey->key; + } + + if ((update_class == dns_rdataclass_in || + update_class == dns_rdataclass_none) && + rdata.type == dns_rdatatype_ptr) + { + result = dns_rdata_tostruct(&rdata, &ptr, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + target = &ptr.ptr; + } + + if ((update_class == dns_rdataclass_in || + update_class == dns_rdataclass_none) && + rdata.type == dns_rdatatype_srv) + { + result = dns_rdata_tostruct(&rdata, &srv, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + target = &srv.target; + } + + if (update_class == dns_rdataclass_any && + zoneclass == dns_rdataclass_in && + (rdata.type == dns_rdatatype_ptr || + rdata.type == dns_rdatatype_srv)) + { + ssu_check_t ssuinfo; + + ssuinfo.name = name; + ssuinfo.table = ssutable; + ssuinfo.signer = client->signer; + ssuinfo.addr = &netaddr; + ssuinfo.aclenv = env; + ssuinfo.tcp = TCPCLIENT(client); + ssuinfo.key = tsigkey; + + result = foreach_rr(db, ver, name, rdata.type, + dns_rdatatype_none, + ssu_checkrr, &ssuinfo); + if (result != ISC_R_SUCCESS) { + FAILC(DNS_R_REFUSED, + "rejected by secure update"); + } + } else if (target != NULL && + update_class == dns_rdataclass_none) + { + bool flag; + CHECK(rr_exists(db, ver, name, &rdata, &flag)); + if (flag && + !dns_ssutable_checkrules( + ssutable, client->signer, name, + &netaddr, TCPCLIENT(client), env, + rdata.type, target, tsigkey, + &rules[rule])) + { + FAILC(DNS_R_REFUSED, + "rejected by secure update"); + } + } else if (rdata.type != dns_rdatatype_any) { + if (!dns_ssutable_checkrules( + ssutable, client->signer, name, + &netaddr, TCPCLIENT(client), env, + rdata.type, target, tsigkey, + &rules[rule])) + { + FAILC(DNS_R_REFUSED, "rejected by " + "secure update"); + } + } else { + if (!ssu_checkall(db, ver, name, ssutable, + client->signer, &netaddr, env, + TCPCLIENT(client), tsigkey)) + { + FAILC(DNS_R_REFUSED, "rejected by " + "secure update"); + } + } + } + } + if (result != ISC_R_NOMORE) { + FAIL(result); + } + + update_log(client, zone, LOGLEVEL_DEBUG, "update section prescan OK"); + + result = isc_quota_attach(&client->manager->sctx->updquota, + &(isc_quota_t *){ NULL }); + if (result != ISC_R_SUCCESS) { + update_log(client, zone, LOGLEVEL_PROTOCOL, + "update failed: too many DNS UPDATEs queued (%s)", + isc_result_totext(result)); + ns_stats_increment(client->manager->sctx->nsstats, + ns_statscounter_updatequota); + CHECK(DNS_R_DROP); + } event = (update_event_t *)isc_event_allocate( client->mctx, client, DNS_EVENT_UPDATE, update_action, NULL, sizeof(*event)); event->zone = zone; event->result = ISC_R_SUCCESS; + event->rules = rules; + event->ruleslen = ruleslen; + rules = NULL; INSIST(client->nupdates == 0); client->nupdates++; @@ -1659,6 +1900,20 @@ send_update_event(ns_client_t *client, dns_zone_t *zone) { dns_zone_gettask(zone, &zonetask); isc_task_send(zonetask, ISC_EVENT_PTR(&event)); +failure: + if (db != NULL) { + dns_db_closeversion(db, &ver, false); + dns_db_detach(&db); + } + + if (rules != NULL) { + isc_mem_put(mctx, rules, sizeof(*rules) * ruleslen); + } + + if (ssutable != NULL) { + dns_ssutable_detach(&ssutable); + } + return (result); } @@ -1766,9 +2021,6 @@ ns_update_start(ns_client_t *client, isc_nmhandle_t *handle, break; case dns_zone_secondary: case dns_zone_mirror: - CHECK(checkupdateacl(client, dns_zone_getforwardacl(zone), - "update forwarding", zonename, true, - false)); dns_message_clonebuffer(client->message); CHECK(send_forward_event(client, zone)); break; @@ -1779,16 +2031,21 @@ ns_update_start(ns_client_t *client, isc_nmhandle_t *handle, failure: if (result == DNS_R_REFUSED) { - INSIST(dns_zone_gettype(zone) == dns_zone_secondary || - dns_zone_gettype(zone) == dns_zone_mirror); inc_stats(client, zone, ns_statscounter_updaterej); } + /* * We failed without having sent an update event to the zone. * We are still in the client task context, so we can * simply give an error response without switching tasks. */ - respond(client, result); + if (result == DNS_R_DROP) { + ns_client_drop(client, result); + isc_nmhandle_detach(&client->reqhandle); + } else { + respond(client, result); + } + if (zone != NULL) { dns_zone_detach(&zone); } @@ -2622,6 +2879,8 @@ update_action(isc_task_t *task, isc_event_t *event) { update_event_t *uev = (update_event_t *)event; dns_zone_t *zone = uev->zone; ns_client_t *client = (ns_client_t *)event->ev_arg; + const dns_ssurule_t **rules = uev->rules; + size_t rule = 0, ruleslen = uev->ruleslen; isc_result_t result; dns_db_t *db = NULL; dns_dbversion_t *oldver = NULL; @@ -2633,7 +2892,7 @@ update_action(isc_task_t *task, isc_event_t *event) { dns_rdatatype_t covers; dns_message_t *request = client->message; dns_rdataclass_t zoneclass; - dns_name_t *zonename; + dns_name_t *zonename = NULL; dns_ssutable_t *ssutable = NULL; dns_fixedname_t tmpnamefixed; dns_name_t *tmpname = NULL; @@ -2645,10 +2904,6 @@ update_action(isc_task_t *task, isc_event_t *event) { dns_ttl_t maxttl = 0; uint32_t maxrecords; uint64_t records; - dns_aclenv_t *env = client->manager->aclenv; - size_t ruleslen = 0; - size_t rule; - const dns_ssurule_t **rules = NULL; INSIST(event->ev_type == DNS_EVENT_UPDATE); @@ -2659,14 +2914,7 @@ update_action(isc_task_t *task, isc_event_t *event) { zonename = dns_db_origin(db); zoneclass = dns_db_class(db); dns_zone_getssutable(zone, &ssutable); - - /* - * Update message processing can leak record existence information - * so check that we are allowed to query this zone. Additionally - * if we would refuse all updates for this zone we bail out here. - */ - CHECK(checkqueryacl(client, dns_zone_getqueryacl(zone), zonename, - dns_zone_getupdateacl(zone), ssutable)); + options = dns_zone_getoptions(zone); /* * Get old and new versions now that queryacl has been checked. @@ -2801,205 +3049,10 @@ update_action(isc_task_t *task, isc_event_t *event) { update_log(client, zone, LOGLEVEL_DEBUG, "prerequisites are OK"); - /* - * Check Requestor's Permissions. It seems a bit silly to do this - * only after prerequisite testing, but that is what RFC2136 says. - */ - if (ssutable == NULL) { - CHECK(checkupdateacl(client, dns_zone_getupdateacl(zone), - "update", zonename, false, false)); - } else if (client->signer == NULL && !TCPCLIENT(client)) { - CHECK(checkupdateacl(client, NULL, "update", zonename, false, - true)); - } - - if (dns_zone_getupdatedisabled(zone)) { - FAILC(DNS_R_REFUSED, "dynamic update temporarily disabled " - "because the zone is frozen. Use " - "'rndc thaw' to re-enable updates."); - } - - /* - * Perform the Update Section Prescan. - */ - if (ssutable != NULL) { - ruleslen = request->counts[DNS_SECTION_UPDATE]; - rules = isc_mem_get(mctx, sizeof(*rules) * ruleslen); - memset(rules, 0, sizeof(*rules) * ruleslen); - } - - for (rule = 0, - result = dns_message_firstname(request, DNS_SECTION_UPDATE); - result == ISC_R_SUCCESS; - rule++, result = dns_message_nextname(request, DNS_SECTION_UPDATE)) - { - dns_name_t *name = NULL; - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_ttl_t ttl; - dns_rdataclass_t update_class; - - INSIST(ssutable == NULL || rule < ruleslen); - - get_current_rr(request, DNS_SECTION_UPDATE, zoneclass, &name, - &rdata, &covers, &ttl, &update_class); - - if (!dns_name_issubdomain(name, zonename)) { - FAILC(DNS_R_NOTZONE, "update RR is outside zone"); - } - if (update_class == zoneclass) { - /* - * Check for meta-RRs. The RFC2136 pseudocode says - * check for ANY|AXFR|MAILA|MAILB, but the text adds - * "or any other QUERY metatype" - */ - if (dns_rdatatype_ismeta(rdata.type)) { - FAILC(DNS_R_FORMERR, "meta-RR in update"); - } - result = dns_zone_checknames(zone, name, &rdata); - if (result != ISC_R_SUCCESS) { - FAIL(DNS_R_REFUSED); - } - } else if (update_class == dns_rdataclass_any) { - if (ttl != 0 || rdata.length != 0 || - (dns_rdatatype_ismeta(rdata.type) && - rdata.type != dns_rdatatype_any)) - { - FAILC(DNS_R_FORMERR, "meta-RR in update"); - } - } else if (update_class == dns_rdataclass_none) { - if (ttl != 0 || dns_rdatatype_ismeta(rdata.type)) { - FAILC(DNS_R_FORMERR, "meta-RR in update"); - } - } else { - update_log(client, zone, ISC_LOG_WARNING, - "update RR has incorrect class %d", - update_class); - FAIL(DNS_R_FORMERR); - } - - /* - * draft-ietf-dnsind-simple-secure-update-01 says - * "Unlike traditional dynamic update, the client - * is forbidden from updating NSEC records." - */ - if (rdata.type == dns_rdatatype_nsec3) { - FAILC(DNS_R_REFUSED, "explicit NSEC3 updates are not " - "allowed " - "in secure zones"); - } else if (rdata.type == dns_rdatatype_nsec) { - FAILC(DNS_R_REFUSED, "explicit NSEC updates are not " - "allowed " - "in secure zones"); - } else if (rdata.type == dns_rdatatype_rrsig && - !dns_name_equal(name, zonename)) - { - FAILC(DNS_R_REFUSED, "explicit RRSIG updates are " - "currently " - "not supported in secure zones " - "except " - "at the apex"); - } - - if (ssutable != NULL) { - isc_netaddr_t netaddr; - dns_name_t *target = NULL; - dst_key_t *tsigkey = NULL; - dns_rdata_ptr_t ptr; - dns_rdata_in_srv_t srv; - - isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr); - - if (client->message->tsigkey != NULL) { - tsigkey = client->message->tsigkey->key; - } - - if ((update_class == dns_rdataclass_in || - update_class == dns_rdataclass_none) && - rdata.type == dns_rdatatype_ptr) - { - result = dns_rdata_tostruct(&rdata, &ptr, NULL); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - target = &ptr.ptr; - } - - if ((update_class == dns_rdataclass_in || - update_class == dns_rdataclass_none) && - rdata.type == dns_rdatatype_srv) - { - result = dns_rdata_tostruct(&rdata, &srv, NULL); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - target = &srv.target; - } - - if (update_class == dns_rdataclass_any && - zoneclass == dns_rdataclass_in && - (rdata.type == dns_rdatatype_ptr || - rdata.type == dns_rdatatype_srv)) - { - ssu_check_t ssuinfo; - - ssuinfo.name = name; - ssuinfo.table = ssutable; - ssuinfo.signer = client->signer; - ssuinfo.addr = &netaddr; - ssuinfo.aclenv = env; - ssuinfo.tcp = TCPCLIENT(client); - ssuinfo.key = tsigkey; - - result = foreach_rr(db, ver, name, rdata.type, - dns_rdatatype_none, - ssu_checkrr, &ssuinfo); - if (result != ISC_R_SUCCESS) { - FAILC(DNS_R_REFUSED, - "rejected by secure update"); - } - } else if (target != NULL && - update_class == dns_rdataclass_none) - { - bool flag; - CHECK(rr_exists(db, ver, name, &rdata, &flag)); - if (flag && - !dns_ssutable_checkrules( - ssutable, client->signer, name, - &netaddr, TCPCLIENT(client), env, - rdata.type, target, tsigkey, - &rules[rule])) - { - FAILC(DNS_R_REFUSED, - "rejected by secure update"); - } - } else if (rdata.type != dns_rdatatype_any) { - if (!dns_ssutable_checkrules( - ssutable, client->signer, name, - &netaddr, TCPCLIENT(client), env, - rdata.type, target, tsigkey, - &rules[rule])) - { - FAILC(DNS_R_REFUSED, "rejected by " - "secure update"); - } - } else { - if (!ssu_checkall(db, ver, name, ssutable, - client->signer, &netaddr, env, - TCPCLIENT(client), tsigkey)) - { - FAILC(DNS_R_REFUSED, "rejected by " - "secure update"); - } - } - } - } - if (result != ISC_R_NOMORE) { - FAIL(result); - } - - update_log(client, zone, LOGLEVEL_DEBUG, "update section prescan OK"); - /* * Process the Update Section. */ - - options = dns_zone_getoptions(zone); + INSIST(ssutable == NULL || rules != NULL); for (rule = 0, result = dns_message_firstname(request, DNS_SECTION_UPDATE); result == ISC_R_SUCCESS; @@ -3461,10 +3514,7 @@ update_action(isc_task_t *task, isc_event_t *event) { if (result == ISC_R_SUCCESS && records > maxrecords) { update_log(client, zone, ISC_LOG_ERROR, "records in zone (%" PRIu64 ") " - "exceeds" - " max-" - "records" - " (%u)", + "exceeds max-records (%u)", records, maxrecords); result = DNS_R_TOOMANYRECORDS; goto failure; @@ -3669,6 +3719,7 @@ updatedone_action(isc_task_t *task, isc_event_t *event) { respond(client, uev->result); + isc_quota_detach(&(isc_quota_t *){ &client->manager->sctx->updquota }); isc_event_free(&event); isc_nmhandle_detach(&client->updatehandle); } @@ -3685,6 +3736,8 @@ forward_fail(isc_task_t *task, isc_event_t *event) { INSIST(client->nupdates > 0); client->nupdates--; respond(client, DNS_R_SERVFAIL); + + isc_quota_detach(&(isc_quota_t *){ &client->manager->sctx->updquota }); isc_event_free(&event); isc_nmhandle_detach(&client->updatehandle); } @@ -3722,6 +3775,8 @@ forward_done(isc_task_t *task, isc_event_t *event) { client->nupdates--; ns_client_sendraw(client, uev->answer); dns_message_detach(&uev->answer); + + isc_quota_detach(&(isc_quota_t *){ &client->manager->sctx->updquota }); isc_event_free(&event); isc_nmhandle_detach(&client->reqhandle); isc_nmhandle_detach(&client->updatehandle); @@ -3757,6 +3812,24 @@ send_forward_event(ns_client_t *client, dns_zone_t *zone) { update_event_t *event = NULL; isc_task_t *zonetask = NULL; + result = checkupdateacl(client, dns_zone_getforwardacl(zone), + "update forwarding", dns_zone_getorigin(zone), + true, false); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = isc_quota_attach(&client->manager->sctx->updquota, + &(isc_quota_t *){ NULL }); + if (result != ISC_R_SUCCESS) { + update_log(client, zone, LOGLEVEL_PROTOCOL, + "update failed: too many DNS UPDATEs queued (%s)", + isc_result_totext(result)); + ns_stats_increment(client->manager->sctx->nsstats, + ns_statscounter_updatequota); + return (DNS_R_DROP); + } + event = (update_event_t *)isc_event_allocate( client->mctx, client, DNS_EVENT_UPDATE, forward_action, NULL, sizeof(*event));