From 361ec726cb1c90448517794de414f083e7e1629d Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Wed, 3 Jul 2019 16:42:15 +1000 Subject: [PATCH 1/7] allow per type record counts to be specified --- bin/named/zoneconf.c | 39 ++++++++++++++----- .../system/checkconf/bad-update-policy16.conf | 18 +++++++++ .../checkconf/good-update-policy13.conf | 18 +++++++++ lib/bind9/check.c | 25 +++++++++++- lib/dns/include/dns/ssu.h | 9 ++++- lib/dns/ssu.c | 29 +++++++------- 6 files changed, 110 insertions(+), 28 deletions(-) create mode 100644 bin/tests/system/checkconf/bad-update-policy16.conf create mode 100644 bin/tests/system/checkconf/good-update-policy13.conf diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index 7cad6c6b7a..785d392e7b 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -237,7 +237,7 @@ configure_zone_ssutable(const cfg_obj_t *zconfig, dns_zone_t *zone, dns_ssumatchtype_t mtype = dns_ssumatchtype_name; dns_fixedname_t fname, fident; isc_buffer_t b; - dns_rdatatype_t *types; + dns_ssuruletype_t *types; unsigned int i, n; str = cfg_obj_asstring(mode); @@ -290,7 +290,7 @@ configure_zone_ssutable(const cfg_obj_t *zconfig, dns_zone_t *zone, if (n == 0) { types = NULL; } else { - types = isc_mem_get(mctx, n * sizeof(dns_rdatatype_t)); + types = isc_mem_get(mctx, n * sizeof(*types)); } i = 0; @@ -298,22 +298,43 @@ configure_zone_ssutable(const cfg_obj_t *zconfig, dns_zone_t *zone, element2 = cfg_list_next(element2)) { const cfg_obj_t *typeobj; + const char *bracket; isc_textregion_t r; + unsigned long max = 0; INSIST(i < n); typeobj = cfg_listelt_value(element2); str = cfg_obj_asstring(typeobj); DE_CONST(str, r.base); - r.length = strlen(str); - result = dns_rdatatype_fromtext(&types[i++], &r); + bracket = strchr(str, '(' /*)*/); + if (bracket != NULL) { + char *end = NULL; + r.length = bracket - str; + max = strtoul(bracket + 1, &end, 10); + if (max > 0xffff || end[0] != /*(*/ ')' || + end[1] != 0) { + cfg_obj_log(identity, named_g_lctx, + ISC_LOG_ERROR, + "'%s' is not a valid count", + bracket); + isc_mem_put(mctx, types, + n * sizeof(*types)); + goto cleanup; + } + } else { + r.length = strlen(str); + } + types[i].max = max; + + result = dns_rdatatype_fromtext(&types[i++].type, &r); if (result != ISC_R_SUCCESS) { cfg_obj_log(identity, named_g_lctx, ISC_LOG_ERROR, - "'%s' is not a valid type", str); - isc_mem_put(mctx, types, - n * sizeof(dns_rdatatype_t)); + "'%.*s' is not a valid type", + (int)r.length, str); + isc_mem_put(mctx, types, n * sizeof(types)); goto cleanup; } } @@ -323,7 +344,7 @@ configure_zone_ssutable(const cfg_obj_t *zconfig, dns_zone_t *zone, table, grant, dns_fixedname_name(&fident), mtype, dns_fixedname_name(&fname), n, types); if (types != NULL) { - isc_mem_put(mctx, types, n * sizeof(dns_rdatatype_t)); + isc_mem_put(mctx, types, n * sizeof(*types)); } if (result != ISC_R_SUCCESS) { goto cleanup; @@ -336,7 +357,7 @@ configure_zone_ssutable(const cfg_obj_t *zconfig, dns_zone_t *zone, * update-policy { grant zonesub any; }; */ if (autoddns) { - dns_rdatatype_t any = dns_rdatatype_any; + dns_ssuruletype_t any = { dns_rdatatype_any, 0 }; if (named_g_server->session_keyname == NULL) { isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, diff --git a/bin/tests/system/checkconf/bad-update-policy16.conf b/bin/tests/system/checkconf/bad-update-policy16.conf new file mode 100644 index 0000000000..6870e65e62 --- /dev/null +++ b/bin/tests/system/checkconf/bad-update-policy16.conf @@ -0,0 +1,18 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +zone "example.com" { + type master; + file "example.com.db"; + update-policy { + grant * tcp-self . "ptr(10 )"; + }; +}; diff --git a/bin/tests/system/checkconf/good-update-policy13.conf b/bin/tests/system/checkconf/good-update-policy13.conf new file mode 100644 index 0000000000..e67e01f319 --- /dev/null +++ b/bin/tests/system/checkconf/good-update-policy13.conf @@ -0,0 +1,18 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +zone "example.com" { + type master; + file "example.com.db"; + update-policy { + grant * tcp-self . ptr(1); + }; +}; diff --git a/lib/bind9/check.c b/lib/bind9/check.c index fdb0f42378..9e761f4832 100644 --- a/lib/bind9/check.c +++ b/lib/bind9/check.c @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -1850,15 +1851,35 @@ check_update_policy(const cfg_obj_t *policy, isc_log_t *logctx) { element2 = cfg_list_next(element2)) { const cfg_obj_t *typeobj; + const char *bracket; typeobj = cfg_listelt_value(element2); DE_CONST(cfg_obj_asstring(typeobj), r.base); - r.length = strlen(r.base); + + bracket = strchr(r.base, '(' /*)*/); + if (bracket != NULL) { + char *end = NULL; + unsigned long max; + + r.length = bracket - r.base; + max = strtoul(bracket + 1, &end, 10); + if (max > 0xffff || end[0] != /*(*/ ')' || + end[1] != 0) { + cfg_obj_log(typeobj, logctx, + ISC_LOG_ERROR, + "'%s' is not a valid count", + bracket); + result = DNS_R_SYNTAX; + } + } else { + r.length = strlen(r.base); + } tresult = dns_rdatatype_fromtext(&type, &r); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(typeobj, logctx, ISC_LOG_ERROR, - "'%s' is not a valid type", r.base); + "'%.*s' is not a valid type", + (int)r.length, r.base); result = tresult; } } diff --git a/lib/dns/include/dns/ssu.h b/lib/dns/include/dns/ssu.h index e2ead5bc05..19f49f0b5b 100644 --- a/lib/dns/include/dns/ssu.h +++ b/lib/dns/include/dns/ssu.h @@ -47,6 +47,11 @@ typedef enum { dns_ssumatchtype_dlz = 16 /* intentionally higher than _max */ } dns_ssumatchtype_t; +typedef struct dns_ssuruletype { + dns_rdatatype_t type; /* type allowed */ + unsigned int max; /* maximum number of records allowed. */ +} dns_ssuruletype_t; + isc_result_t dns_ssutable_create(isc_mem_t *mctx, dns_ssutable_t **table); /*%< @@ -103,7 +108,7 @@ isc_result_t dns_ssutable_addrule(dns_ssutable_t *table, bool grant, const dns_name_t *identity, dns_ssumatchtype_t matchtype, const dns_name_t *name, unsigned int ntypes, - dns_rdatatype_t *types); + dns_ssuruletype_t *types); /*%< * Adds a new rule to a simple-secure-update rule table. The rule * either grants or denies update privileges of an identity (or set of @@ -191,7 +196,7 @@ dns_name_t * dns_ssurule_name(const dns_ssurule_t *rule); /*% Accessor functions to extract rule components */ unsigned int -dns_ssurule_types(const dns_ssurule_t *rule, dns_rdatatype_t **types); +dns_ssurule_types(const dns_ssurule_t *rule, dns_ssuruletype_t **types); isc_result_t dns_ssutable_firstrule(const dns_ssutable_t *table, dns_ssurule_t **rule); diff --git a/lib/dns/ssu.c b/lib/dns/ssu.c index 47758b8cf6..c2647b6619 100644 --- a/lib/dns/ssu.c +++ b/lib/dns/ssu.c @@ -44,7 +44,7 @@ struct dns_ssurule { dns_name_t *identity; /*%< the identity to match */ dns_name_t *name; /*%< the name being updated */ unsigned int ntypes; /*%< number of data types covered */ - dns_rdatatype_t *types; /*%< the data types. Can include */ + dns_ssuruletype_t *types; /*%< the data types. Can include */ /* ANY. if NULL, defaults to all */ /* types except SIG, SOA, and NS */ ISC_LINK(dns_ssurule_t) link; @@ -86,15 +86,16 @@ destroy(dns_ssutable_t *table) { dns_ssurule_t *rule = ISC_LIST_HEAD(table->rules); if (rule->identity != NULL) { dns_name_free(rule->identity, mctx); - isc_mem_put(mctx, rule->identity, sizeof(dns_name_t)); + isc_mem_put(mctx, rule->identity, + sizeof(*rule->identity)); } if (rule->name != NULL) { dns_name_free(rule->name, mctx); - isc_mem_put(mctx, rule->name, sizeof(dns_name_t)); + isc_mem_put(mctx, rule->name, sizeof(*rule->name)); } if (rule->types != NULL) { isc_mem_put(mctx, rule->types, - rule->ntypes * sizeof(dns_rdatatype_t)); + rule->ntypes * sizeof(*rule->types)); } ISC_LIST_UNLINK(table->rules, rule, link); rule->magic = 0; @@ -133,7 +134,7 @@ isc_result_t dns_ssutable_addrule(dns_ssutable_t *table, bool grant, const dns_name_t *identity, dns_ssumatchtype_t matchtype, const dns_name_t *name, unsigned int ntypes, - dns_rdatatype_t *types) { + dns_ssuruletype_t *types) { dns_ssurule_t *rule; isc_mem_t *mctx; @@ -149,7 +150,7 @@ dns_ssutable_addrule(dns_ssutable_t *table, bool grant, } mctx = table->mctx; - rule = isc_mem_get(mctx, sizeof(dns_ssurule_t)); + rule = isc_mem_get(mctx, sizeof(*rule)); rule->identity = NULL; rule->name = NULL; @@ -157,11 +158,11 @@ dns_ssutable_addrule(dns_ssutable_t *table, bool grant, rule->grant = grant; - rule->identity = isc_mem_get(mctx, sizeof(dns_name_t)); + rule->identity = isc_mem_get(mctx, sizeof(*rule->identity)); dns_name_init(rule->identity, NULL); dns_name_dup(identity, mctx, rule->identity); - rule->name = isc_mem_get(mctx, sizeof(dns_name_t)); + rule->name = isc_mem_get(mctx, sizeof(*rule->name)); dns_name_init(rule->name, NULL); dns_name_dup(name, mctx, rule->name); @@ -169,9 +170,8 @@ dns_ssutable_addrule(dns_ssutable_t *table, bool grant, rule->ntypes = ntypes; if (ntypes > 0) { - rule->types = isc_mem_get(mctx, - ntypes * sizeof(dns_rdatatype_t)); - memmove(rule->types, types, ntypes * sizeof(dns_rdatatype_t)); + rule->types = isc_mem_get(mctx, ntypes * sizeof(*rule->types)); + memmove(rule->types, types, ntypes * sizeof(*rule->types)); } else { rule->types = NULL; } @@ -513,8 +513,8 @@ dns_ssutable_checkrules(dns_ssutable_t *table, const dns_name_t *signer, } } else { for (i = 0; i < rule->ntypes; i++) { - if (rule->types[i] == dns_rdatatype_any || - rule->types[i] == type) { + if (rule->types[i].type == dns_rdatatype_any || + rule->types[i].type == type) { break; } } @@ -553,7 +553,7 @@ dns_ssurule_name(const dns_ssurule_t *rule) { } unsigned int -dns_ssurule_types(const dns_ssurule_t *rule, dns_rdatatype_t **types) { +dns_ssurule_types(const dns_ssurule_t *rule, dns_ssuruletype_t **types) { REQUIRE(VALID_SSURULE(rule)); REQUIRE(types != NULL && *types != NULL); *types = rule->types; @@ -599,7 +599,6 @@ dns_ssutable_createdlz(isc_mem_t *mctx, dns_ssutable_t **tablep, rule->identity = NULL; rule->name = NULL; - rule->types = NULL; rule->grant = true; rule->matchtype = dns_ssumatchtype_dlz; rule->ntypes = 0; From 79de6edde82f4417359a49eae1b3f756044d950f Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Wed, 3 Jul 2019 17:03:13 +1000 Subject: [PATCH 2/7] allow grant rules to be retrieved --- lib/dns/include/dns/ssu.h | 2 +- lib/dns/ssu.c | 18 ++++++++++-------- lib/ns/update.c | 39 ++++++++++++++++++++++++++++++--------- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/lib/dns/include/dns/ssu.h b/lib/dns/include/dns/ssu.h index 19f49f0b5b..b3a6e829e0 100644 --- a/lib/dns/include/dns/ssu.h +++ b/lib/dns/include/dns/ssu.h @@ -141,7 +141,7 @@ bool dns_ssutable_checkrules(dns_ssutable_t *table, const dns_name_t *signer, const dns_name_t *name, const isc_netaddr_t *addr, bool tcp, const dns_aclenv_t *env, dns_rdatatype_t type, - const dst_key_t *key); + const dst_key_t *key, const dns_ssurule_t **rulep); /*%< * Checks that the attempted update of (name, type) is allowed according * to the rules specified in the simple-secure-update rule table. If diff --git a/lib/dns/ssu.c b/lib/dns/ssu.c index c2647b6619..39de705776 100644 --- a/lib/dns/ssu.c +++ b/lib/dns/ssu.c @@ -39,8 +39,7 @@ struct dns_ssurule { unsigned int magic; bool grant; /*%< is this a grant or a deny? */ - dns_ssumatchtype_t matchtype; /*%< which type of pattern match? - * */ + dns_ssumatchtype_t matchtype; /*%< which type of pattern match? */ dns_name_t *identity; /*%< the identity to match */ dns_name_t *name; /*%< the name being updated */ unsigned int ntypes; /*%< number of data types covered */ @@ -284,15 +283,15 @@ bool dns_ssutable_checkrules(dns_ssutable_t *table, const dns_name_t *signer, const dns_name_t *name, const isc_netaddr_t *addr, bool tcp, const dns_aclenv_t *env, dns_rdatatype_t type, - const dst_key_t *key) { - dns_ssurule_t *rule; - unsigned int i; + const dst_key_t *key, const dns_ssurule_t **rulep) { dns_fixedname_t fixed; - dns_name_t *wildcard; - dns_name_t *tcpself; dns_name_t *stfself; - isc_result_t result; + dns_name_t *tcpself; + dns_name_t *wildcard; + dns_ssurule_t *rule; int match; + isc_result_t result; + unsigned int i; REQUIRE(VALID_SSUTABLE(table)); REQUIRE(signer == NULL || dns_name_isabsolute(signer)); @@ -522,6 +521,9 @@ dns_ssutable_checkrules(dns_ssutable_t *table, const dns_name_t *signer, continue; } } + if (rule->grant && rulep != NULL) { + *rulep = rule; + } return (rule->grant); } diff --git a/lib/ns/update.c b/lib/ns/update.c index 3c88115e0b..2a4355a0a3 100644 --- a/lib/ns/update.c +++ b/lib/ns/update.c @@ -899,8 +899,9 @@ typedef struct { static isc_result_t ssu_checkrule(void *data, dns_rdataset_t *rrset) { - ssu_check_t *ssuinfo = data; bool result; + const dns_ssurule_t *rule = NULL; + ssu_check_t *ssuinfo = data; /* * If we're deleting all records, it's ok to delete RRSIG and NSEC even @@ -910,9 +911,10 @@ ssu_checkrule(void *data, dns_rdataset_t *rrset) { rrset->type == dns_rdatatype_nsec) { return (ISC_R_SUCCESS); } - result = dns_ssutable_checkrules( - ssuinfo->table, ssuinfo->signer, ssuinfo->name, ssuinfo->addr, - ssuinfo->tcp, ssuinfo->aclenv, rrset->type, ssuinfo->key); + result = dns_ssutable_checkrules(ssuinfo->table, ssuinfo->signer, + ssuinfo->name, ssuinfo->addr, + ssuinfo->tcp, ssuinfo->aclenv, + rrset->type, ssuinfo->key, &rule); return (result == true ? ISC_R_SUCCESS : ISC_R_FAILURE); } @@ -2565,6 +2567,9 @@ update_action(isc_task_t *task, isc_event_t *event) { uint64_t records; dns_aclenv_t *env = ns_interfacemgr_getaclenv(client->manager->interface->mgr); + size_t ruleslen = 0; + size_t rule; + const dns_ssurule_t **rules = NULL; INSIST(event->ev_type == DNS_EVENT_UPDATE); @@ -2739,15 +2744,24 @@ update_action(isc_task_t *task, isc_event_t *event) { /* * 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 (result = dns_message_firstname(request, DNS_SECTION_UPDATE); + for (rule = 0, + result = dns_message_firstname(request, DNS_SECTION_UPDATE); result == ISC_R_SUCCESS; - result = dns_message_nextname(request, DNS_SECTION_UPDATE)) + 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); @@ -2820,7 +2834,7 @@ update_action(isc_task_t *task, isc_event_t *event) { if (!dns_ssutable_checkrules( ssutable, client->signer, name, &netaddr, TCPCLIENT(client), env, - rdata.type, tsigkey)) + rdata.type, tsigkey, &rules[rule])) { FAILC(DNS_R_REFUSED, "rejected by " "secure update"); @@ -2847,9 +2861,10 @@ update_action(isc_task_t *task, isc_event_t *event) { */ options = dns_zone_getoptions(zone); - for (result = dns_message_firstname(request, DNS_SECTION_UPDATE); + for (rule = 0, + result = dns_message_firstname(request, DNS_SECTION_UPDATE); result == ISC_R_SUCCESS; - result = dns_message_nextname(request, DNS_SECTION_UPDATE)) + rule++, result = dns_message_nextname(request, DNS_SECTION_UPDATE)) { dns_name_t *name = NULL; dns_rdata_t rdata = DNS_RDATA_INIT; @@ -2857,6 +2872,8 @@ update_action(isc_task_t *task, isc_event_t *event) { dns_rdataclass_t update_class; bool flag; + INSIST(ssutable == NULL || rule < ruleslen); + get_current_rr(request, DNS_SECTION_UPDATE, zoneclass, &name, &rdata, &covers, &ttl, &update_class); @@ -3420,6 +3437,10 @@ common: dns_db_detach(&db); } + if (rules != NULL) { + isc_mem_put(mctx, rules, sizeof(*rules) * ruleslen); + } + if (ssutable != NULL) { dns_ssutable_detach(&ssutable); } From 919a9ece25be4ab8673bf104be7c18b7cef6a346 Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Thu, 4 Jul 2019 15:24:20 +1000 Subject: [PATCH 3/7] enforce record count maximums --- lib/dns/include/dns/ssu.h | 13 +++++++++++++ lib/dns/ssu.c | 18 ++++++++++++++++++ lib/dns/win32/libdns.def.in | 3 ++- lib/ns/update.c | 31 +++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/lib/dns/include/dns/ssu.h b/lib/dns/include/dns/ssu.h index b3a6e829e0..cc463a9d6b 100644 --- a/lib/dns/include/dns/ssu.h +++ b/lib/dns/include/dns/ssu.h @@ -185,19 +185,32 @@ dns_ssutable_checkrules(dns_ssutable_t *table, const dns_name_t *signer, /*% Accessor functions to extract rule components */ bool dns_ssurule_isgrant(const dns_ssurule_t *rule); + /*% Accessor functions to extract rule components */ dns_name_t * dns_ssurule_identity(const dns_ssurule_t *rule); + /*% Accessor functions to extract rule components */ unsigned int dns_ssurule_matchtype(const dns_ssurule_t *rule); + /*% Accessor functions to extract rule components */ dns_name_t * dns_ssurule_name(const dns_ssurule_t *rule); + /*% Accessor functions to extract rule components */ unsigned int dns_ssurule_types(const dns_ssurule_t *rule, dns_ssuruletype_t **types); +unsigned int +dns_ssurule_max(const dns_ssurule_t *rule, dns_rdatatype_t type); +/*%< + * Returns the maximum number of records configured for type `type`. + * If no maximum has been configured for `type` but one has been + * configured for ANY, return that value instead. Otherwise, return + * zero, which implies "unlimited". + */ + isc_result_t dns_ssutable_firstrule(const dns_ssutable_t *table, dns_ssurule_t **rule); /*%< diff --git a/lib/dns/ssu.c b/lib/dns/ssu.c index 39de705776..9a601febe8 100644 --- a/lib/dns/ssu.c +++ b/lib/dns/ssu.c @@ -562,6 +562,24 @@ dns_ssurule_types(const dns_ssurule_t *rule, dns_ssuruletype_t **types) { return (rule->ntypes); } +unsigned int +dns_ssurule_max(const dns_ssurule_t *rule, dns_rdatatype_t type) { + unsigned int i; + unsigned int max = 0; + + REQUIRE(VALID_SSURULE(rule)); + + for (i = 0; i < rule->ntypes; i++) { + if (rule->types[i].type == dns_rdatatype_any) { + max = rule->types[i].max; + } + if (rule->types[i].type == type) { + return (rule->types[i].max); + } + } + return (max); +} + isc_result_t dns_ssutable_firstrule(const dns_ssutable_t *table, dns_ssurule_t **rule) { REQUIRE(VALID_SSUTABLE(table)); diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index 629fe53333..2d1c23b2ed 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -1019,9 +1019,10 @@ dns_soa_setretry dns_soa_setserial dns_ssu_external_match dns_ssu_mtypefromstring -dns_ssurule_isgrant dns_ssurule_identity +dns_ssurule_isgrant dns_ssurule_matchtype +dns_ssurule_max dns_ssurule_name dns_ssurule_types dns_ssutable_firstrule diff --git a/lib/ns/update.c b/lib/ns/update.c index 2a4355a0a3..84e1c7e129 100644 --- a/lib/ns/update.c +++ b/lib/ns/update.c @@ -753,6 +753,17 @@ cleanup_node: typedef bool rr_predicate(dns_rdata_t *update_rr, dns_rdata_t *db_rr); +static isc_result_t +count_action(void *data, rr_t *rr) { + unsigned int *ui = (unsigned int *)data; + + UNUSED(rr); + + (*ui)++; + + return (ISC_R_SUCCESS); +} + /*% * Helper function for rrset_exists(). */ @@ -2878,6 +2889,8 @@ update_action(isc_task_t *task, isc_event_t *event) { &rdata, &covers, &ttl, &update_class); if (update_class == zoneclass) { + unsigned int max = 0; + /* * RFC1123 doesn't allow MF and MD in master zones. */ @@ -3003,6 +3016,24 @@ update_action(isc_task_t *task, isc_event_t *event) { } } + if (rules != NULL && rules[rule] != NULL) { + max = dns_ssurule_max(rules[rule], rdata.type); + } + if (max != 0) { + unsigned int count = 0; + CHECK(foreach_rr(db, ver, name, rdata.type, + covers, count_action, &count)); + if (count >= max) { + update_log(client, zone, + LOGLEVEL_PROTOCOL, + "attempt to add more " + "records than permitted by " + "policy max=%u", + max); + continue; + } + } + if (isc_log_wouldlog(ns_lctx, LOGLEVEL_PROTOCOL)) { char namestr[DNS_NAME_FORMATSIZE]; char typestr[DNS_RDATATYPE_FORMATSIZE]; From 04da28bdff25727fa3c475a6cbfe695aa1918b28 Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Thu, 4 Jul 2019 15:58:13 +1000 Subject: [PATCH 4/7] test max records policy --- bin/tests/system/nsupdate/clean.sh | 2 + bin/tests/system/nsupdate/ns6/named.conf.in | 2 +- bin/tests/system/nsupdate/tests.sh | 59 +++++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/bin/tests/system/nsupdate/clean.sh b/bin/tests/system/nsupdate/clean.sh index ef5ec56e65..9b8af4a7ff 100644 --- a/bin/tests/system/nsupdate/clean.sh +++ b/bin/tests/system/nsupdate/clean.sh @@ -60,3 +60,5 @@ rm -f update.out.* rm -f check.out.* rm -f update.out.* rm -f ns*/managed-keys.bind* ns*/*.mkeys* +rm -f nextpart.out.* +rm -f */named.run.prev diff --git a/bin/tests/system/nsupdate/ns6/named.conf.in b/bin/tests/system/nsupdate/ns6/named.conf.in index da64d061e4..b4ec11a326 100644 --- a/bin/tests/system/nsupdate/ns6/named.conf.in +++ b/bin/tests/system/nsupdate/ns6/named.conf.in @@ -34,5 +34,5 @@ controls { zone "in-addr.arpa" { type master; file "in-addr.db"; - update-policy { grant * tcp-self . PTR; }; + update-policy { grant * tcp-self . PTR(1) ANY(2) A; }; }; diff --git a/bin/tests/system/nsupdate/tests.sh b/bin/tests/system/nsupdate/tests.sh index 1cbe04b15e..4c5b739113 100755 --- a/bin/tests/system/nsupdate/tests.sh +++ b/bin/tests/system/nsupdate/tests.sh @@ -1031,6 +1031,65 @@ grep "UPDATE, status: NOERROR" nsupdate.out-$n > /dev/null 2>&1 || ret=1 grep "UPDATE, status: FORMERR" nsupdate.out-$n > /dev/null 2>&1 || ret=1 [ $ret = 0 ] || { echo_i "failed"; status=1; } +n=`expr $n + 1` +ret=0 +echo_i "check that max records is enforced ($n)" +nextpart ns6/named.run > /dev/null +$NSUPDATE -v > nsupdate.out.$n 2>&1 << END +server 10.53.0.6 ${PORT} +local 10.53.0.5 +update del 5.0.53.10.in-addr.arpa. +update add 5.0.53.10.in-addr.arpa. 600 PTR localhost. +update add 5.0.53.10.in-addr.arpa. 600 PTR other. +send +END +$DIG $DIGOPTS @10.53.0.6 \ + +tcp +noadd +nosea +nostat +noquest +nocomm +nocmd \ + -x 10.53.0.5 > dig.out.ns6.$n +# the policy is 'grant * tcp-self . PTR(1) ANY(2) A;' so only the +# first PTR record should be added. +grep localhost. dig.out.ns6.$n > /dev/null 2>&1 || ret=1 +grep other. dig.out.ns6.$n > /dev/null 2>&1 && ret=1 +nextpart ns6/named.run > nextpart.out.$n +grep "attempt to add more records than permitted by policy" nextpart.out.$n > /dev/null || ret=1 +if test $ret -ne 0 +then +echo_i "failed"; status=1 +fi + +n=`expr $n + 1` +ret=0 +echo_i "check that max records for ANY is enforced ($n)" +nextpart ns6/named.run > /dev/null +$NSUPDATE -v > nsupdate.out.$n 2>&1 << END +server 10.53.0.6 ${PORT} +local 10.53.0.5 +update del 5.0.53.10.in-addr.arpa. +update add 5.0.53.10.in-addr.arpa. 600 A 1.2.3.4 +update add 5.0.53.10.in-addr.arpa. 600 A 1.2.3.3 +update add 5.0.53.10.in-addr.arpa. 600 A 1.2.3.2 +update add 5.0.53.10.in-addr.arpa. 600 AAAA ::ffff:1.2.3.4 +update add 5.0.53.10.in-addr.arpa. 600 AAAA ::ffff:1.2.3.3 +update add 5.0.53.10.in-addr.arpa. 600 AAAA ::ffff:1.2.3.2 +send +END +$DIG $DIGOPTS @10.53.0.6 \ + +tcp +noadd +nosea +nostat +noquest +nocomm +nocmd \ + ANY -x 10.53.0.5 > dig.out.ns6.test$n +nextpart ns6/named.run > nextpart.out.test$n +grep "attempt to add more records than permitted by policy" nextpart.out.test$n > /dev/null || ret=1 +# the policy is 'grant * tcp-self . PTR(1) ANY(2) A;' so all the A +# records should have been added as there is no limit and the first 2 +# of the AAAA records added as they match ANY(2). +c1=$(awk '$4 == "A" { print }' dig.out.ns6.test$n | wc -l) +c2=$(awk '$4 == "AAAA" { print }' dig.out.ns6.test$n | wc -l) +test "$c1" -eq 3 -a "$c2" -eq 2 || ret=1 +grep "::ffff:1.2.3.2" dig.out.ns6.test$n && ret=1 +if test $ret -ne 0 +then +echo_i "failed"; status=1 +fi + if $FEATURETEST --gssapi ; then n=`expr $n + 1` ret=0 From 0dd6d67fab88a445b392ccbe63bff842b40239fe Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Thu, 13 Feb 2020 14:53:20 +1100 Subject: [PATCH 5/7] document type count limits --- doc/arm/reference.rst | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index cb94dd6d40..0a3c4a7734 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -8,16 +8,6 @@ See the COPYRIGHT file distributed with this work for additional information regarding copyright ownership. -.. - Copyright (C) Internet Systems Consortium, Inc. ("ISC") - - 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 http://mozilla.org/MPL/2.0/. - - See the COPYRIGHT file distributed with this work for additional - information regarding copyright ownership. - .. Reference: BIND 9 Configuration Reference @@ -5708,6 +5698,29 @@ never be updated). Note that when an attempt is made to delete all records associated with a name, the rules are checked for each existing record type. +If the type is immediately followed by a number in parentheses, +that number is the maximum number of records of that type permitted +to exist in the RRset after an update has been applied. For example, +``PTR(1)`` indicates that only one PTR record is allowed. If an +attempt is made to add two PTR records in an update, the second one +will be silently discarded. If a PTR record already exists, both +new records will be silently discarded. + +If type ANY is specified with a limit, then that limit applies to +all types that are not otherwise specified. For example, ``A PTR(1) +ANY(2)`` indicates that an unlimited number of A records can exist, +but only one PTR record, and no more than two of any other type. + +Typical use with rule ``grant * tcp-self . PTR(1);`` in the zone +2.0.192.IN-ADDR.ARPA:: + + nsupdate -v < Date: Fri, 6 Mar 2020 14:51:22 +1100 Subject: [PATCH 6/7] Add CHANGES note --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 95d9a03b4e..c3003f1179 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +5410. [func] Add the ability to specify per-type record count + limits in an "update-policy" statement, which + are enforced when adding records via UPDATE. + [GL #1657] + 5409. [performance] When looking up NSEC3 data in a zone database, skip the check for empty non-terminal nodes; the NSEC3 tree doesn't have any. [GL #1834] From 8f9dc72e63533232e3a4024e6853bb305d253569 Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Fri, 6 Mar 2020 14:54:28 +1100 Subject: [PATCH 7/7] Add release note entry --- doc/notes/notes-current.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/notes/notes-current.rst b/doc/notes/notes-current.rst index e115c38105..3c31be1022 100644 --- a/doc/notes/notes-current.rst +++ b/doc/notes/notes-current.rst @@ -58,6 +58,10 @@ New Features - ``dig`` and other tools can now print the Extended DNS Error (EDE) option when it appears in a request or response. [GL #1834] +- Per-type record count limits can now be specified in ``update-policy`` + statements, to limit the number of records of a particular type + that can be added to a domain name via dynamic update. [GL #1657] + Feature Changes ~~~~~~~~~~~~~~~