2018-06-15 09:59:20 +02:00
|
|
|
/*
|
|
|
|
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: MPL-2.0
|
2021-06-03 08:37:05 +02:00
|
|
|
*
|
2018-06-15 09:59:20 +02:00
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*! \file */
|
|
|
|
|
2018-03-28 14:19:37 +02:00
|
|
|
#include <inttypes.h>
|
2018-06-15 09:59:20 +02:00
|
|
|
#include <stdarg.h>
|
2018-04-17 08:29:14 -07:00
|
|
|
#include <stdbool.h>
|
2018-06-15 09:59:20 +02:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include <isc/base32.h>
|
|
|
|
#include <isc/buffer.h>
|
|
|
|
#include <isc/heap.h>
|
|
|
|
#include <isc/iterated_hash.h>
|
2018-06-15 09:59:20 +02:00
|
|
|
#include <isc/log.h>
|
2018-06-15 09:59:20 +02:00
|
|
|
#include <isc/mem.h>
|
|
|
|
#include <isc/region.h>
|
|
|
|
#include <isc/result.h>
|
|
|
|
#include <isc/types.h>
|
|
|
|
#include <isc/util.h>
|
2020-02-12 13:59:18 +01:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
#include <dns/db.h>
|
|
|
|
#include <dns/dbiterator.h>
|
|
|
|
#include <dns/dnssec.h>
|
|
|
|
#include <dns/fixedname.h>
|
2018-06-28 13:38:39 +02:00
|
|
|
#include <dns/keytable.h>
|
2018-06-15 09:59:20 +02:00
|
|
|
#include <dns/keyvalues.h>
|
|
|
|
#include <dns/name.h>
|
|
|
|
#include <dns/nsec.h>
|
|
|
|
#include <dns/nsec3.h>
|
|
|
|
#include <dns/rdata.h>
|
|
|
|
#include <dns/rdataset.h>
|
|
|
|
#include <dns/rdatasetiter.h>
|
|
|
|
#include <dns/rdatastruct.h>
|
|
|
|
#include <dns/rdatatype.h>
|
|
|
|
#include <dns/secalg.h>
|
|
|
|
#include <dns/types.h>
|
2018-06-15 09:59:20 +02:00
|
|
|
#include <dns/zone.h>
|
2018-06-15 09:59:20 +02:00
|
|
|
#include <dns/zoneverify.h>
|
|
|
|
|
|
|
|
#include <dst/dst.h>
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
typedef struct vctx {
|
2018-06-15 09:59:20 +02:00
|
|
|
isc_mem_t *mctx;
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_zone_t *zone;
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_db_t *db;
|
|
|
|
dns_dbversion_t *ver;
|
|
|
|
dns_name_t *origin;
|
2018-06-28 13:38:39 +02:00
|
|
|
dns_keytable_t *secroots;
|
2018-04-17 08:29:14 -07:00
|
|
|
bool goodksk;
|
|
|
|
bool goodzsk;
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_t keyset;
|
|
|
|
dns_rdataset_t keysigs;
|
|
|
|
dns_rdataset_t soaset;
|
|
|
|
dns_rdataset_t soasigs;
|
|
|
|
dns_rdataset_t nsecset;
|
|
|
|
dns_rdataset_t nsecsigs;
|
|
|
|
dns_rdataset_t nsec3paramset;
|
|
|
|
dns_rdataset_t nsec3paramsigs;
|
Support for DST_ALG_PRIVATEDNS and DST_ALG_PRIVATEOID
The algorithm values PRIVATEDNS and PRIVATEOID are placeholders,
signifying that the actual algorithm identifier is encoded into the
key data. Keys using this mechanism are now supported.
- The algorithm values PRIVATEDNS and PRIVATEOID cannot be used to
build a key file name; dst_key_buildfilename() will assert if
they are used.
- The DST key values for private algorithms are higher than 255.
Since DST_ALG_MAXALG now exceeds 256, algorithm arrays that were
previously hardcoded to size 256 have been resized.
- New mnemonic/text conversion functions have been added.
dst_algorithm_{fromtext,totext,format} can handle algorithm
identifiers encoded in PRIVATEDNS and PRIVATEOID keys, as well
as the traditional algorithm identifiers. (Note: The existing
dns_secalg_{fromtext,totext,format} functions are similar, but
do *not* support PRIVATEDNS and PRIVATEOID. In most cases, the
new functions have taken the place of the old ones, but in a few
cases the old version is still appropriate.)
- dns_private{oid,dns}_{fromtext,totext,format} converts between
DST algorithm values and the mnemonic strings for algorithms
implemented using PRIVATEDNS or PRIVATEOID. (E.g., "RSASHA256OID").
- dst_algorithm_tosecalg() returns the DNSSEC algorithm identifier
that applies for a given DST algorithm. For PRIVATEDNS- or
PRIVATEOID- based algorithms, the result will be PRIVATEDNS or
PRIVATEOID, respectively.
- dst_algorithm_fromprivatedns() and dst_algorithm_fromprivateoid()
return the DST algorithm identifier for an encoded algorithm in
wire format, represented as in DNS name or an object identifier,
respectively.
- dst_algorithm_fromdata() is a front-end for the above; it extracts
the private algorithm identifier encoded at the begining of a
block of key or signature data, and returns the matching DST
algorithm number.
- dst_key_fromdns() and dst_key_frombuffer() now work with keys
that have PRIVATEDNS and PRIVATEOID algorithm identifiers at the
beginning.
2025-04-16 11:31:41 +10:00
|
|
|
unsigned char revoked_ksk[DST_MAX_ALGS];
|
|
|
|
unsigned char revoked_zsk[DST_MAX_ALGS];
|
|
|
|
unsigned char standby_ksk[DST_MAX_ALGS];
|
|
|
|
unsigned char standby_zsk[DST_MAX_ALGS];
|
|
|
|
unsigned char ksk_algorithms[DST_MAX_ALGS];
|
|
|
|
unsigned char zsk_algorithms[DST_MAX_ALGS];
|
|
|
|
unsigned char bad_algorithms[DST_MAX_ALGS];
|
|
|
|
unsigned char act_algorithms[DST_MAX_ALGS];
|
2018-06-15 09:59:20 +02:00
|
|
|
isc_heap_t *expected_chains;
|
|
|
|
isc_heap_t *found_chains;
|
|
|
|
} vctx_t;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
struct nsec3_chain_fixed {
|
2018-03-28 14:19:37 +02:00
|
|
|
uint8_t hash;
|
|
|
|
uint8_t salt_length;
|
|
|
|
uint8_t next_length;
|
|
|
|
uint16_t iterations;
|
2018-06-15 09:59:20 +02:00
|
|
|
/*
|
|
|
|
* The following non-fixed-length data is stored in memory after the
|
|
|
|
* fields declared above for each NSEC3 chain element:
|
|
|
|
*
|
|
|
|
* unsigned char salt[salt_length];
|
|
|
|
* unsigned char owner[next_length];
|
|
|
|
* unsigned char next[next_length];
|
|
|
|
*/
|
|
|
|
};
|
|
|
|
|
2020-06-26 18:53:04 -03:00
|
|
|
/*
|
|
|
|
* Helper function used to calculate length of variable-length
|
|
|
|
* data section in object pointed to by 'chain'.
|
|
|
|
*/
|
2021-10-11 13:43:12 +02:00
|
|
|
static size_t
|
2020-06-26 18:53:04 -03:00
|
|
|
chain_length(struct nsec3_chain_fixed *chain) {
|
|
|
|
return chain->salt_length + 2 * chain->next_length;
|
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
/*%
|
|
|
|
* Log a zone verification error described by 'fmt' and the variable arguments
|
|
|
|
* following it. Either use dns_zone_logv() or print to stderr, depending on
|
|
|
|
* whether the function was invoked from within named or by a standalone tool,
|
|
|
|
* respectively.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
zoneverify_log_error(const vctx_t *vctx, const char *fmt, ...) {
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
if (vctx->zone != NULL) {
|
|
|
|
dns_zone_logv(vctx->zone, DNS_LOGCATEGORY_GENERAL,
|
|
|
|
ISC_LOG_ERROR, NULL, fmt, ap);
|
|
|
|
} else {
|
|
|
|
vfprintf(stderr, fmt, ap);
|
|
|
|
fprintf(stderr, "\n");
|
|
|
|
}
|
|
|
|
va_end(ap);
|
|
|
|
}
|
|
|
|
|
2018-04-17 08:29:14 -07:00
|
|
|
static bool
|
2018-06-15 09:59:20 +02:00
|
|
|
is_delegation(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node,
|
2018-03-28 14:19:37 +02:00
|
|
|
uint32_t *ttlp) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_t nsset;
|
|
|
|
isc_result_t result;
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
if (dns_name_equal(name, vctx->origin)) {
|
2018-04-17 08:29:14 -07:00
|
|
|
return false;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
dns_rdataset_init(&nsset);
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
|
|
dns_rdatatype_ns, 0, 0, &nsset, NULL);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (dns_rdataset_isassociated(&nsset)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
if (ttlp != NULL) {
|
2018-06-15 09:59:20 +02:00
|
|
|
*ttlp = nsset.ttl;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_disassociate(&nsset);
|
|
|
|
}
|
|
|
|
|
2018-04-17 08:29:14 -07:00
|
|
|
return result == ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*%
|
2018-04-17 08:29:14 -07:00
|
|
|
* Return true if version 'ver' of database 'db' contains a DNAME RRset at
|
|
|
|
* 'node'; return false otherwise.
|
2018-06-15 09:59:20 +02:00
|
|
|
*/
|
2018-04-17 08:29:14 -07:00
|
|
|
static bool
|
2018-06-15 09:59:20 +02:00
|
|
|
has_dname(const vctx_t *vctx, dns_dbnode_t *node) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_t dnameset;
|
|
|
|
isc_result_t result;
|
|
|
|
|
|
|
|
dns_rdataset_init(&dnameset);
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
|
|
dns_rdatatype_dname, 0, 0, &dnameset,
|
|
|
|
NULL);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (dns_rdataset_isassociated(&dnameset)) {
|
|
|
|
dns_rdataset_disassociate(&dnameset);
|
|
|
|
}
|
|
|
|
|
2018-04-17 08:29:14 -07:00
|
|
|
return result == ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2018-04-17 08:29:14 -07:00
|
|
|
static bool
|
2018-06-15 09:59:20 +02:00
|
|
|
goodsig(const vctx_t *vctx, dns_rdata_t *sigrdata, const dns_name_t *name,
|
2020-11-25 17:52:22 +11:00
|
|
|
dst_key_t **dstkeys, size_t nkeys, dns_rdataset_t *rdataset) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdata_rrsig_t sig;
|
|
|
|
isc_result_t result;
|
2025-04-01 00:12:52 +11:00
|
|
|
dst_algorithm_t algorithm;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
result = dns_rdata_tostruct(sigrdata, &sig, NULL);
|
2018-06-15 09:59:20 +02:00
|
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2025-04-01 00:12:52 +11:00
|
|
|
algorithm = dst_algorithm_fromdata(sig.algorithm, sig.signature,
|
|
|
|
sig.siglen);
|
|
|
|
|
2020-11-25 17:52:22 +11:00
|
|
|
for (size_t key = 0; key < nkeys; key++) {
|
2025-04-01 00:12:52 +11:00
|
|
|
if (algorithm != dst_key_alg(dstkeys[key]) ||
|
2020-11-25 17:52:22 +11:00
|
|
|
sig.keyid != dst_key_id(dstkeys[key]) ||
|
2018-06-15 09:59:20 +02:00
|
|
|
!dns_name_equal(&sig.signer, vctx->origin))
|
|
|
|
{
|
2018-06-15 09:59:20 +02:00
|
|
|
continue;
|
|
|
|
}
|
2020-11-25 17:52:22 +11:00
|
|
|
result = dns_dnssec_verify(name, rdataset, dstkeys[key], false,
|
2025-04-28 17:22:18 +02:00
|
|
|
vctx->mctx, sigrdata, NULL);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD) {
|
2018-04-17 08:29:14 -07:00
|
|
|
return true;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
}
|
2018-04-17 08:29:14 -07:00
|
|
|
return false;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2018-04-17 08:29:14 -07:00
|
|
|
static bool
|
2018-07-31 14:52:48 +10:00
|
|
|
nsec_bitmap_equal(dns_rdata_nsec_t *nsec, dns_rdata_t *rdata) {
|
|
|
|
isc_result_t result;
|
|
|
|
dns_rdata_nsec_t tmpnsec;
|
|
|
|
|
|
|
|
result = dns_rdata_tostruct(rdata, &tmpnsec, NULL);
|
|
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
|
|
|
|
|
|
if (nsec->len != tmpnsec.len ||
|
|
|
|
memcmp(nsec->typebits, tmpnsec.typebits, nsec->len) != 0)
|
|
|
|
{
|
2018-04-17 08:29:14 -07:00
|
|
|
return false;
|
2018-07-31 14:52:48 +10:00
|
|
|
}
|
2018-04-17 08:29:14 -07:00
|
|
|
return true;
|
2018-07-31 14:52:48 +10:00
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
static isc_result_t
|
2018-06-15 09:59:20 +02:00
|
|
|
verifynsec(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node,
|
|
|
|
const dns_name_t *nextname, isc_result_t *vresult) {
|
2018-06-15 09:59:20 +02:00
|
|
|
unsigned char buffer[DNS_NSEC_BUFFERSIZE];
|
|
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
|
|
char nextbuf[DNS_NAME_FORMATSIZE];
|
|
|
|
char found[DNS_NAME_FORMATSIZE];
|
|
|
|
dns_rdataset_t rdataset;
|
|
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
|
|
dns_rdata_t tmprdata = DNS_RDATA_INIT;
|
|
|
|
dns_rdata_nsec_t nsec;
|
|
|
|
isc_result_t result;
|
|
|
|
|
|
|
|
dns_rdataset_init(&rdataset);
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
|
|
dns_rdatatype_nsec, 0, 0, &rdataset, NULL);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx, "Missing NSEC record for %s",
|
|
|
|
namebuf);
|
|
|
|
*vresult = ISC_R_FAILURE;
|
|
|
|
result = ISC_R_SUCCESS;
|
|
|
|
goto done;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
result = dns_rdataset_first(&rdataset);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx, "dns_rdataset_first(): %s",
|
|
|
|
isc_result_totext(result));
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
dns_rdataset_current(&rdataset, &rdata);
|
|
|
|
result = dns_rdata_tostruct(&rdata, &nsec, NULL);
|
2018-06-15 09:59:20 +02:00
|
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
2018-07-31 14:52:48 +10:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
/* Check next name is consistent */
|
2018-06-15 09:59:20 +02:00
|
|
|
if (!dns_name_equal(&nsec.next, nextname)) {
|
|
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
|
|
dns_name_format(nextname, nextbuf, sizeof(nextbuf));
|
|
|
|
dns_name_format(&nsec.next, found, sizeof(found));
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx,
|
|
|
|
"Bad NSEC record for %s, next name "
|
|
|
|
"mismatch (expected:%s, found:%s)",
|
|
|
|
namebuf, nextbuf, found);
|
|
|
|
*vresult = ISC_R_FAILURE;
|
|
|
|
goto done;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-07-31 14:52:48 +10:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
/* Check bit map is consistent */
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_nsec_buildrdata(vctx->db, vctx->ver, node, nextname,
|
|
|
|
buffer, &tmprdata);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx, "dns_nsec_buildrdata(): %s",
|
|
|
|
isc_result_totext(result));
|
|
|
|
goto done;
|
|
|
|
}
|
2018-07-31 14:52:48 +10:00
|
|
|
if (!nsec_bitmap_equal(&nsec, &tmprdata)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx,
|
|
|
|
"Bad NSEC record for %s, bit map "
|
|
|
|
"mismatch",
|
|
|
|
namebuf);
|
|
|
|
*vresult = ISC_R_FAILURE;
|
|
|
|
goto done;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_rdataset_next(&rdataset);
|
|
|
|
if (result != ISC_R_NOMORE) {
|
|
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx, "Multiple NSEC records for %s",
|
|
|
|
namebuf);
|
|
|
|
*vresult = ISC_R_FAILURE;
|
|
|
|
goto done;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
*vresult = ISC_R_SUCCESS;
|
|
|
|
result = ISC_R_SUCCESS;
|
|
|
|
|
|
|
|
done:
|
|
|
|
if (dns_rdataset_isassociated(&rdataset)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_disassociate(&rdataset);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
static isc_result_t
|
2018-06-15 09:59:20 +02:00
|
|
|
check_no_rrsig(const vctx_t *vctx, const dns_rdataset_t *rdataset,
|
|
|
|
const dns_name_t *name, dns_dbnode_t *node) {
|
2018-06-15 09:59:20 +02:00
|
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
2018-06-15 09:59:20 +02:00
|
|
|
char typebuf[DNS_RDATATYPE_FORMATSIZE];
|
2025-03-26 22:49:03 -07:00
|
|
|
dns_rdataset_t sigrdataset = DNS_RDATASET_INIT;
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdatasetiter_t *rdsiter = NULL;
|
|
|
|
isc_result_t result;
|
|
|
|
|
|
|
|
dns_rdataset_init(&sigrdataset);
|
2022-11-16 10:47:40 +11:00
|
|
|
result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s",
|
|
|
|
isc_result_totext(result));
|
|
|
|
return result;
|
|
|
|
}
|
2025-03-26 22:49:03 -07:00
|
|
|
DNS_RDATASETITER_FOREACH(rdsiter) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdatasetiter_current(rdsiter, &sigrdataset);
|
|
|
|
if (sigrdataset.type == dns_rdatatype_rrsig &&
|
|
|
|
sigrdataset.covers == rdataset->type)
|
|
|
|
{
|
2020-06-18 14:35:09 -03:00
|
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
|
|
dns_rdatatype_format(rdataset->type, typebuf,
|
|
|
|
sizeof(typebuf));
|
|
|
|
zoneverify_log_error(
|
|
|
|
vctx,
|
|
|
|
"Warning: Found unexpected signatures "
|
|
|
|
"for %s/%s",
|
|
|
|
namebuf, typebuf);
|
2018-06-15 09:59:20 +02:00
|
|
|
break;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_disassociate(&sigrdataset);
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
if (dns_rdataset_isassociated(&sigrdataset)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_disassociate(&sigrdataset);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdatasetiter_destroy(&rdsiter);
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
return ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2018-04-17 08:29:14 -07:00
|
|
|
static bool
|
2018-06-15 09:59:20 +02:00
|
|
|
chain_compare(void *arg1, void *arg2) {
|
|
|
|
struct nsec3_chain_fixed *e1 = arg1, *e2 = arg2;
|
|
|
|
/*
|
|
|
|
* Do each element in turn to get a stable sort.
|
|
|
|
*/
|
2018-06-15 09:59:20 +02:00
|
|
|
if (e1->hash < e2->hash) {
|
2018-04-17 08:29:14 -07:00
|
|
|
return true;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
if (e1->hash > e2->hash) {
|
2018-04-17 08:29:14 -07:00
|
|
|
return false;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
if (e1->iterations < e2->iterations) {
|
2018-04-17 08:29:14 -07:00
|
|
|
return true;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
if (e1->iterations > e2->iterations) {
|
2018-04-17 08:29:14 -07:00
|
|
|
return false;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
if (e1->salt_length < e2->salt_length) {
|
2018-04-17 08:29:14 -07:00
|
|
|
return true;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
if (e1->salt_length > e2->salt_length) {
|
2018-04-17 08:29:14 -07:00
|
|
|
return false;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
if (e1->next_length < e2->next_length) {
|
2018-04-17 08:29:14 -07:00
|
|
|
return true;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
if (e1->next_length > e2->next_length) {
|
2018-04-17 08:29:14 -07:00
|
|
|
return false;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2020-06-26 18:53:04 -03:00
|
|
|
if (memcmp(e1 + 1, e2 + 1, chain_length(e1)) < 0) {
|
2018-04-17 08:29:14 -07:00
|
|
|
return true;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-04-17 08:29:14 -07:00
|
|
|
return false;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2018-04-17 08:29:14 -07:00
|
|
|
static bool
|
2018-06-15 09:59:20 +02:00
|
|
|
chain_equal(const struct nsec3_chain_fixed *e1,
|
2020-06-26 18:53:04 -03:00
|
|
|
const struct nsec3_chain_fixed *e2, size_t data_length) {
|
2018-06-15 09:59:20 +02:00
|
|
|
if (e1->hash != e2->hash) {
|
2018-04-17 08:29:14 -07:00
|
|
|
return false;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
if (e1->iterations != e2->iterations) {
|
2018-04-17 08:29:14 -07:00
|
|
|
return false;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
if (e1->salt_length != e2->salt_length) {
|
2018-04-17 08:29:14 -07:00
|
|
|
return false;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
if (e1->next_length != e2->next_length) {
|
2018-04-17 08:29:14 -07:00
|
|
|
return false;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2020-06-26 18:53:04 -03:00
|
|
|
|
|
|
|
return memcmp(e1 + 1, e2 + 1, data_length) == 0;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2021-10-19 11:46:37 +02:00
|
|
|
static void
|
2018-06-15 09:59:20 +02:00
|
|
|
record_nsec3(const vctx_t *vctx, const unsigned char *rawhash,
|
|
|
|
const dns_rdata_nsec3_t *nsec3, isc_heap_t *chains) {
|
2021-10-19 11:46:37 +02:00
|
|
|
struct nsec3_chain_fixed *element = NULL;
|
|
|
|
unsigned char *cp = NULL;
|
2018-06-15 09:59:20 +02:00
|
|
|
size_t len;
|
|
|
|
|
|
|
|
len = sizeof(*element) + nsec3->next_length * 2 + nsec3->salt_length;
|
|
|
|
|
2023-08-23 11:05:14 +02:00
|
|
|
element = isc_mem_get(vctx->mctx, len);
|
|
|
|
*element = (struct nsec3_chain_fixed){
|
|
|
|
.hash = nsec3->hash,
|
|
|
|
.salt_length = nsec3->salt_length,
|
|
|
|
.next_length = nsec3->next_length,
|
|
|
|
.iterations = nsec3->iterations,
|
|
|
|
};
|
2018-06-15 09:59:20 +02:00
|
|
|
cp = (unsigned char *)(element + 1);
|
|
|
|
memmove(cp, nsec3->salt, nsec3->salt_length);
|
|
|
|
cp += nsec3->salt_length;
|
|
|
|
memmove(cp, rawhash, nsec3->next_length);
|
|
|
|
cp += nsec3->next_length;
|
|
|
|
memmove(cp, nsec3->next, nsec3->next_length);
|
2021-10-19 11:46:37 +02:00
|
|
|
isc_heap_insert(chains, element);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2020-06-18 16:49:40 -03:00
|
|
|
/*
|
|
|
|
* Check whether any NSEC3 within 'rdataset' matches the parameters in
|
|
|
|
* 'nsec3param'.
|
|
|
|
*/
|
2018-06-15 09:59:20 +02:00
|
|
|
static isc_result_t
|
2020-06-18 16:49:40 -03:00
|
|
|
find_nsec3_match(const dns_rdata_nsec3param_t *nsec3param,
|
|
|
|
dns_rdataset_t *rdataset, size_t rhsize,
|
|
|
|
dns_rdata_nsec3_t *nsec3_match) {
|
2018-06-15 09:59:20 +02:00
|
|
|
/*
|
|
|
|
* Find matching NSEC3 record.
|
|
|
|
*/
|
2025-03-21 23:32:27 -07:00
|
|
|
DNS_RDATASET_FOREACH(rdataset) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
|
|
dns_rdataset_current(rdataset, &rdata);
|
2025-03-21 23:32:27 -07:00
|
|
|
dns_rdata_tostruct(&rdata, nsec3_match, NULL);
|
2020-06-18 16:49:40 -03:00
|
|
|
if (nsec3_match->hash == nsec3param->hash &&
|
|
|
|
nsec3_match->next_length == rhsize &&
|
|
|
|
nsec3_match->iterations == nsec3param->iterations &&
|
|
|
|
nsec3_match->salt_length == nsec3param->salt_length &&
|
|
|
|
memcmp(nsec3_match->salt, nsec3param->salt,
|
2018-06-15 09:59:20 +02:00
|
|
|
nsec3param->salt_length) == 0)
|
|
|
|
{
|
2020-06-18 16:49:40 -03:00
|
|
|
return ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2020-06-18 16:49:40 -03:00
|
|
|
|
2025-03-21 23:32:27 -07:00
|
|
|
return ISC_R_NOTFOUND;
|
2020-06-18 16:49:40 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
static isc_result_t
|
|
|
|
match_nsec3(const vctx_t *vctx, const dns_name_t *name,
|
|
|
|
const dns_rdata_nsec3param_t *nsec3param, dns_rdataset_t *rdataset,
|
|
|
|
const unsigned char types[8192], unsigned int maxtype,
|
|
|
|
const unsigned char *rawhash, size_t rhsize,
|
|
|
|
isc_result_t *vresult) {
|
|
|
|
unsigned char cbm[8244];
|
|
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
|
|
dns_rdata_nsec3_t nsec3;
|
|
|
|
isc_result_t result;
|
|
|
|
unsigned int len;
|
|
|
|
|
|
|
|
result = find_nsec3_match(nsec3param, rdataset, rhsize, &nsec3);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx, "Missing NSEC3 record for %s",
|
|
|
|
namebuf);
|
|
|
|
*vresult = result;
|
|
|
|
return ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check the type list.
|
|
|
|
*/
|
|
|
|
len = dns_nsec_compressbitmap(cbm, types, maxtype);
|
|
|
|
if (nsec3.len != len || memcmp(cbm, nsec3.typebits, len) != 0) {
|
|
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx,
|
|
|
|
"Bad NSEC3 record for %s, bit map "
|
|
|
|
"mismatch",
|
|
|
|
namebuf);
|
|
|
|
*vresult = ISC_R_FAILURE;
|
|
|
|
return ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Record chain.
|
|
|
|
*/
|
2021-10-19 11:46:37 +02:00
|
|
|
record_nsec3(vctx, rawhash, &nsec3, vctx->expected_chains);
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Make sure there is only one NSEC3 record with this set of
|
|
|
|
* parameters.
|
|
|
|
*/
|
|
|
|
for (result = dns_rdataset_next(rdataset); result == ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_rdataset_next(rdataset))
|
|
|
|
{
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
|
|
dns_rdataset_current(rdataset, &rdata);
|
|
|
|
result = dns_rdata_tostruct(&rdata, &nsec3, NULL);
|
2018-06-15 09:59:20 +02:00
|
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (nsec3.hash == nsec3param->hash &&
|
|
|
|
nsec3.iterations == nsec3param->iterations &&
|
|
|
|
nsec3.salt_length == nsec3param->salt_length &&
|
|
|
|
memcmp(nsec3.salt, nsec3param->salt, nsec3.salt_length) ==
|
2018-06-15 09:59:20 +02:00
|
|
|
0)
|
|
|
|
{
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx,
|
|
|
|
"Multiple NSEC3 records with the "
|
|
|
|
"same parameter set for %s",
|
|
|
|
namebuf);
|
|
|
|
*vresult = DNS_R_DUPLICATE;
|
|
|
|
return ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_NOMORE) {
|
2018-06-15 09:59:20 +02:00
|
|
|
return result;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
*vresult = ISC_R_SUCCESS;
|
|
|
|
|
|
|
|
return ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2018-04-17 08:29:14 -07:00
|
|
|
static bool
|
2018-06-15 09:59:20 +02:00
|
|
|
innsec3params(const dns_rdata_nsec3_t *nsec3, dns_rdataset_t *nsec3paramset) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdata_nsec3param_t nsec3param;
|
|
|
|
isc_result_t result;
|
|
|
|
|
2025-03-21 23:32:27 -07:00
|
|
|
DNS_RDATASET_FOREACH(nsec3paramset) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
|
|
|
|
|
|
dns_rdataset_current(nsec3paramset, &rdata);
|
|
|
|
result = dns_rdata_tostruct(&rdata, &nsec3param, NULL);
|
2018-06-15 09:59:20 +02:00
|
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (nsec3param.flags == 0 && nsec3param.hash == nsec3->hash &&
|
|
|
|
nsec3param.iterations == nsec3->iterations &&
|
|
|
|
nsec3param.salt_length == nsec3->salt_length &&
|
|
|
|
memcmp(nsec3param.salt, nsec3->salt, nsec3->salt_length) ==
|
|
|
|
0)
|
|
|
|
{
|
2018-04-17 08:29:14 -07:00
|
|
|
return true;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-04-17 08:29:14 -07:00
|
|
|
return false;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static isc_result_t
|
2018-06-15 09:59:20 +02:00
|
|
|
record_found(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node,
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_t *nsec3paramset) {
|
|
|
|
unsigned char owner[NSEC3_MAX_HASH_LENGTH];
|
|
|
|
dns_rdata_nsec3_t nsec3;
|
|
|
|
dns_rdataset_t rdataset;
|
|
|
|
dns_label_t hashlabel;
|
|
|
|
isc_buffer_t b;
|
|
|
|
isc_result_t result;
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
if (nsec3paramset == NULL || !dns_rdataset_isassociated(nsec3paramset))
|
|
|
|
{
|
2018-06-15 09:59:20 +02:00
|
|
|
return ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
dns_rdataset_init(&rdataset);
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
|
|
dns_rdatatype_nsec3, 0, 0, &rdataset,
|
|
|
|
NULL);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
2018-06-15 09:59:20 +02:00
|
|
|
return ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
dns_name_getlabel(name, 0, &hashlabel);
|
|
|
|
isc_region_consume(&hashlabel, 1);
|
|
|
|
isc_buffer_init(&b, owner, sizeof(owner));
|
|
|
|
result = isc_base32hex_decoderegion(&hashlabel, &b);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
result = ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
goto cleanup;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2025-03-21 23:32:27 -07:00
|
|
|
DNS_RDATASET_FOREACH(&rdataset) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
|
|
dns_rdataset_current(&rdataset, &rdata);
|
|
|
|
result = dns_rdata_tostruct(&rdata, &nsec3, NULL);
|
2018-06-15 09:59:20 +02:00
|
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (nsec3.next_length != isc_buffer_usedlength(&b)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
continue;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2021-10-19 11:46:37 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
/*
|
|
|
|
* We only care about NSEC3 records that match a NSEC3PARAM
|
|
|
|
* record.
|
|
|
|
*/
|
2018-06-15 09:59:20 +02:00
|
|
|
if (!innsec3params(&nsec3, nsec3paramset)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
continue;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Record chain.
|
|
|
|
*/
|
2021-10-19 11:46:37 +02:00
|
|
|
record_nsec3(vctx, owner, &nsec3, vctx->found_chains);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
result = ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
cleanup:
|
|
|
|
dns_rdataset_disassociate(&rdataset);
|
2018-06-15 09:59:20 +02:00
|
|
|
return result;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
static isc_result_t
|
Silence untrusted loop bound on nsec3param.iterations
630
1. tainted_argument: Calling function dns_rdata_tostruct taints argument nsec3param.iterations. [show details]
631 result = dns_rdata_tostruct(nsec3rdata, &nsec3param, NULL);
2. Condition !!(result == 0), taking true branch.
3. Condition !!(result == 0), taking true branch.
632 RUNTIME_CHECK(result == ISC_R_SUCCESS);
633
634 dns_fixedname_init(&fixed);
CID 281425 (#1 of 1): Untrusted loop bound (TAINTED_SCALAR)
4. tainted_data: Passing tainted expression nsec3param.iterations to dns_nsec3_hashname, which uses it as a loop boundary. [show details]
Ensure that tainted values are properly sanitized, by checking that their values are within a permissible range.
635 result = dns_nsec3_hashname(&fixed, rawhash, &rhsize, vctx->origin,
636 vctx->origin, nsec3param.hash,
637 nsec3param.iterations, nsec3param.salt,
638 nsec3param.salt_length);
2021-07-08 15:56:04 +10:00
|
|
|
isoptout(const vctx_t *vctx, const dns_rdata_nsec3param_t *nsec3param,
|
|
|
|
bool *optout) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_t rdataset;
|
|
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
|
|
dns_rdata_nsec3_t nsec3;
|
|
|
|
dns_fixedname_t fixed;
|
|
|
|
dns_name_t *hashname;
|
|
|
|
isc_result_t result;
|
|
|
|
dns_dbnode_t *node = NULL;
|
|
|
|
unsigned char rawhash[NSEC3_MAX_HASH_LENGTH];
|
|
|
|
size_t rhsize = sizeof(rawhash);
|
|
|
|
|
|
|
|
dns_fixedname_init(&fixed);
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_nsec3_hashname(&fixed, rawhash, &rhsize, vctx->origin,
|
Silence untrusted loop bound on nsec3param.iterations
630
1. tainted_argument: Calling function dns_rdata_tostruct taints argument nsec3param.iterations. [show details]
631 result = dns_rdata_tostruct(nsec3rdata, &nsec3param, NULL);
2. Condition !!(result == 0), taking true branch.
3. Condition !!(result == 0), taking true branch.
632 RUNTIME_CHECK(result == ISC_R_SUCCESS);
633
634 dns_fixedname_init(&fixed);
CID 281425 (#1 of 1): Untrusted loop bound (TAINTED_SCALAR)
4. tainted_data: Passing tainted expression nsec3param.iterations to dns_nsec3_hashname, which uses it as a loop boundary. [show details]
Ensure that tainted values are properly sanitized, by checking that their values are within a permissible range.
635 result = dns_nsec3_hashname(&fixed, rawhash, &rhsize, vctx->origin,
636 vctx->origin, nsec3param.hash,
637 nsec3param.iterations, nsec3param.salt,
638 nsec3param.salt_length);
2021-07-08 15:56:04 +10:00
|
|
|
vctx->origin, nsec3param->hash,
|
|
|
|
nsec3param->iterations, nsec3param->salt,
|
|
|
|
nsec3param->salt_length);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx, "dns_nsec3_hashname(): %s",
|
|
|
|
isc_result_totext(result));
|
|
|
|
return result;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
dns_rdataset_init(&rdataset);
|
|
|
|
hashname = dns_fixedname_name(&fixed);
|
2018-04-17 08:29:14 -07:00
|
|
|
result = dns_db_findnsec3node(vctx->db, hashname, false, &node);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result == ISC_R_SUCCESS) {
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
|
|
dns_rdatatype_nsec3, 0, 0,
|
|
|
|
&rdataset, NULL);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
2018-04-17 08:29:14 -07:00
|
|
|
*optout = false;
|
2018-06-15 09:59:20 +02:00
|
|
|
result = ISC_R_SUCCESS;
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
result = dns_rdataset_first(&rdataset);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx, "dns_rdataset_first(): %s",
|
|
|
|
isc_result_totext(result));
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
dns_rdataset_current(&rdataset, &rdata);
|
|
|
|
|
|
|
|
result = dns_rdata_tostruct(&rdata, &nsec3, NULL);
|
2018-06-15 09:59:20 +02:00
|
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
2018-10-11 11:57:57 +02:00
|
|
|
*optout = ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) != 0);
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
done:
|
2018-06-15 09:59:20 +02:00
|
|
|
if (dns_rdataset_isassociated(&rdataset)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_disassociate(&rdataset);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
if (node != NULL) {
|
Decouple database and node lifetimes by adding node-specific vtables
All databases in the codebase follow the same structure: a database is
an associative container from DNS names to nodes, and each node is an
associative container from RR types to RR data.
Each database implementation (qpzone, qpcache, sdlz, builtin, dyndb) has
its own corresponding node type (qpznode, qpcnode, etc). However, some
code needs to work with nodes generically regardless of their specific
type - for example, to acquire locks, manage references, or
register/unregister slabs from the heap.
Currently, these generic node operations are implemented as methods in
the database vtable, which creates problematic coupling between database
and node lifetimes. If a node outlives its parent database, the node
destructor will destroy all RR data, and each RR data destructor will
try to unregister from heaps by calling a virtual function from the
database vtable. Since the database was already freed, this causes a
crash.
This commit breaks the coupling by standardizing the layout of all
database nodes, adding a dedicated vtable for node operations, and
moving node-specific methods from the database vtable to the node
vtable.
2025-06-05 11:51:29 +02:00
|
|
|
dns_db_detachnode(&node);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
return result;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static isc_result_t
|
2018-06-15 09:59:20 +02:00
|
|
|
verifynsec3(const vctx_t *vctx, const dns_name_t *name,
|
2018-04-17 08:29:14 -07:00
|
|
|
const dns_rdata_t *rdata, bool delegation, bool empty,
|
|
|
|
const unsigned char types[8192], unsigned int maxtype,
|
2018-06-15 09:59:20 +02:00
|
|
|
isc_result_t *vresult) {
|
2018-06-15 09:59:20 +02:00
|
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
|
|
char hashbuf[DNS_NAME_FORMATSIZE];
|
|
|
|
dns_rdataset_t rdataset;
|
|
|
|
dns_rdata_nsec3param_t nsec3param;
|
|
|
|
dns_fixedname_t fixed;
|
|
|
|
dns_name_t *hashname;
|
2018-08-24 21:38:06 +02:00
|
|
|
isc_result_t result, tvresult = ISC_R_UNSET;
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_dbnode_t *node = NULL;
|
|
|
|
unsigned char rawhash[NSEC3_MAX_HASH_LENGTH];
|
|
|
|
size_t rhsize = sizeof(rawhash);
|
2018-04-17 08:29:14 -07:00
|
|
|
bool optout = false;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
result = dns_rdata_tostruct(rdata, &nsec3param, NULL);
|
2018-06-15 09:59:20 +02:00
|
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
if (nsec3param.flags != 0) {
|
2018-06-15 09:59:20 +02:00
|
|
|
return ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
if (!dns_nsec3_supportedhash(nsec3param.hash)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
return ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
Silence untrusted loop bound on nsec3param.iterations
630
1. tainted_argument: Calling function dns_rdata_tostruct taints argument nsec3param.iterations. [show details]
631 result = dns_rdata_tostruct(nsec3rdata, &nsec3param, NULL);
2. Condition !!(result == 0), taking true branch.
3. Condition !!(result == 0), taking true branch.
632 RUNTIME_CHECK(result == ISC_R_SUCCESS);
633
634 dns_fixedname_init(&fixed);
CID 281425 (#1 of 1): Untrusted loop bound (TAINTED_SCALAR)
4. tainted_data: Passing tainted expression nsec3param.iterations to dns_nsec3_hashname, which uses it as a loop boundary. [show details]
Ensure that tainted values are properly sanitized, by checking that their values are within a permissible range.
635 result = dns_nsec3_hashname(&fixed, rawhash, &rhsize, vctx->origin,
636 vctx->origin, nsec3param.hash,
637 nsec3param.iterations, nsec3param.salt,
638 nsec3param.salt_length);
2021-07-08 15:56:04 +10:00
|
|
|
if (nsec3param.iterations > DNS_NSEC3_MAXITERATIONS) {
|
|
|
|
result = DNS_R_NSEC3ITERRANGE;
|
|
|
|
zoneverify_log_error(vctx, "verifynsec3: %s",
|
|
|
|
isc_result_totext(result));
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
result = isoptout(vctx, &nsec3param, &optout);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
return result;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
dns_fixedname_init(&fixed);
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_nsec3_hashname(
|
|
|
|
&fixed, rawhash, &rhsize, name, vctx->origin, nsec3param.hash,
|
|
|
|
nsec3param.iterations, nsec3param.salt, nsec3param.salt_length);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx, "dns_nsec3_hashname(): %s",
|
|
|
|
isc_result_totext(result));
|
|
|
|
return result;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* We don't use dns_db_find() here as it works with the chosen
|
|
|
|
* nsec3 chain and we may also be called with uncommitted data
|
|
|
|
* from dnssec-signzone so the secure status of the zone may not
|
|
|
|
* be up to date.
|
|
|
|
*/
|
|
|
|
dns_rdataset_init(&rdataset);
|
|
|
|
hashname = dns_fixedname_name(&fixed);
|
2018-04-17 08:29:14 -07:00
|
|
|
result = dns_db_findnsec3node(vctx->db, hashname, false, &node);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result == ISC_R_SUCCESS) {
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
|
|
dns_rdatatype_nsec3, 0, 0,
|
|
|
|
&rdataset, NULL);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS &&
|
|
|
|
(!delegation || (empty && !optout) ||
|
|
|
|
(!empty && dns_nsec_isset(types, dns_rdatatype_ds))))
|
|
|
|
{
|
|
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
|
|
dns_name_format(hashname, hashbuf, sizeof(hashbuf));
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx, "Missing NSEC3 record for %s (%s)",
|
|
|
|
namebuf, hashbuf);
|
2018-06-15 09:59:20 +02:00
|
|
|
} else if (result == ISC_R_NOTFOUND && delegation && (!empty || optout))
|
|
|
|
{
|
|
|
|
result = ISC_R_SUCCESS;
|
|
|
|
} else if (result == ISC_R_SUCCESS) {
|
2018-06-15 09:59:20 +02:00
|
|
|
result = match_nsec3(vctx, name, &nsec3param, &rdataset, types,
|
2018-06-15 09:59:20 +02:00
|
|
|
maxtype, rawhash, rhsize, &tvresult);
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
result = tvresult;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
*vresult = result;
|
2018-06-15 09:59:20 +02:00
|
|
|
result = ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
done:
|
2018-06-15 09:59:20 +02:00
|
|
|
if (dns_rdataset_isassociated(&rdataset)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_disassociate(&rdataset);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
if (node != NULL) {
|
Decouple database and node lifetimes by adding node-specific vtables
All databases in the codebase follow the same structure: a database is
an associative container from DNS names to nodes, and each node is an
associative container from RR types to RR data.
Each database implementation (qpzone, qpcache, sdlz, builtin, dyndb) has
its own corresponding node type (qpznode, qpcnode, etc). However, some
code needs to work with nodes generically regardless of their specific
type - for example, to acquire locks, manage references, or
register/unregister slabs from the heap.
Currently, these generic node operations are implemented as methods in
the database vtable, which creates problematic coupling between database
and node lifetimes. If a node outlives its parent database, the node
destructor will destroy all RR data, and each RR data destructor will
try to unregister from heaps by calling a virtual function from the
database vtable. Since the database was already freed, this causes a
crash.
This commit breaks the coupling by standardizing the layout of all
database nodes, adding a dedicated vtable for node operations, and
moving node-specific methods from the database vtable to the node
vtable.
2025-06-05 11:51:29 +02:00
|
|
|
dns_db_detachnode(&node);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
return result;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static isc_result_t
|
2018-06-15 09:59:20 +02:00
|
|
|
verifynsec3s(const vctx_t *vctx, const dns_name_t *name,
|
2018-04-17 08:29:14 -07:00
|
|
|
dns_rdataset_t *nsec3paramset, bool delegation, bool empty,
|
|
|
|
const unsigned char types[8192], unsigned int maxtype,
|
2018-06-15 09:59:20 +02:00
|
|
|
isc_result_t *vresult) {
|
2025-03-21 23:32:27 -07:00
|
|
|
isc_result_t result = ISC_R_NOMORE;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2025-03-21 23:32:27 -07:00
|
|
|
DNS_RDATASET_FOREACH(nsec3paramset) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
|
|
|
|
|
|
dns_rdataset_current(nsec3paramset, &rdata);
|
2018-06-15 09:59:20 +02:00
|
|
|
result = verifynsec3(vctx, name, &rdata, delegation, empty,
|
2018-06-15 09:59:20 +02:00
|
|
|
types, maxtype, vresult);
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
if (*vresult != ISC_R_SUCCESS) {
|
2018-06-15 09:59:20 +02:00
|
|
|
break;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2025-03-21 23:32:27 -07:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
static isc_result_t
|
2018-06-15 09:59:20 +02:00
|
|
|
verifyset(vctx_t *vctx, dns_rdataset_t *rdataset, const dns_name_t *name,
|
2020-11-25 17:52:22 +11:00
|
|
|
dns_dbnode_t *node, dst_key_t **dstkeys, size_t nkeys) {
|
Support for DST_ALG_PRIVATEDNS and DST_ALG_PRIVATEOID
The algorithm values PRIVATEDNS and PRIVATEOID are placeholders,
signifying that the actual algorithm identifier is encoded into the
key data. Keys using this mechanism are now supported.
- The algorithm values PRIVATEDNS and PRIVATEOID cannot be used to
build a key file name; dst_key_buildfilename() will assert if
they are used.
- The DST key values for private algorithms are higher than 255.
Since DST_ALG_MAXALG now exceeds 256, algorithm arrays that were
previously hardcoded to size 256 have been resized.
- New mnemonic/text conversion functions have been added.
dst_algorithm_{fromtext,totext,format} can handle algorithm
identifiers encoded in PRIVATEDNS and PRIVATEOID keys, as well
as the traditional algorithm identifiers. (Note: The existing
dns_secalg_{fromtext,totext,format} functions are similar, but
do *not* support PRIVATEDNS and PRIVATEOID. In most cases, the
new functions have taken the place of the old ones, but in a few
cases the old version is still appropriate.)
- dns_private{oid,dns}_{fromtext,totext,format} converts between
DST algorithm values and the mnemonic strings for algorithms
implemented using PRIVATEDNS or PRIVATEOID. (E.g., "RSASHA256OID").
- dst_algorithm_tosecalg() returns the DNSSEC algorithm identifier
that applies for a given DST algorithm. For PRIVATEDNS- or
PRIVATEOID- based algorithms, the result will be PRIVATEDNS or
PRIVATEOID, respectively.
- dst_algorithm_fromprivatedns() and dst_algorithm_fromprivateoid()
return the DST algorithm identifier for an encoded algorithm in
wire format, represented as in DNS name or an object identifier,
respectively.
- dst_algorithm_fromdata() is a front-end for the above; it extracts
the private algorithm identifier encoded at the begining of a
block of key or signature data, and returns the matching DST
algorithm number.
- dst_key_fromdns() and dst_key_frombuffer() now work with keys
that have PRIVATEDNS and PRIVATEOID algorithm identifiers at the
beginning.
2025-04-16 11:31:41 +10:00
|
|
|
unsigned char set_algorithms[DST_MAX_ALGS] = { 0 };
|
2018-06-15 09:59:20 +02:00
|
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
2018-06-15 09:59:20 +02:00
|
|
|
char algbuf[DNS_SECALG_FORMATSIZE];
|
|
|
|
char typebuf[DNS_RDATATYPE_FORMATSIZE];
|
2025-03-26 22:49:03 -07:00
|
|
|
dns_rdataset_t sigrdataset = DNS_RDATASET_INIT;
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdatasetiter_t *rdsiter = NULL;
|
2025-03-26 22:49:03 -07:00
|
|
|
bool match = false;
|
2018-06-15 09:59:20 +02:00
|
|
|
isc_result_t result;
|
|
|
|
|
2022-11-16 10:47:40 +11:00
|
|
|
result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s",
|
|
|
|
isc_result_totext(result));
|
|
|
|
return result;
|
|
|
|
}
|
2025-03-26 22:49:03 -07:00
|
|
|
DNS_RDATASETITER_FOREACH(rdsiter) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdatasetiter_current(rdsiter, &sigrdataset);
|
|
|
|
if (sigrdataset.type == dns_rdatatype_rrsig &&
|
|
|
|
sigrdataset.covers == rdataset->type)
|
|
|
|
{
|
2025-03-26 22:49:03 -07:00
|
|
|
match = true;
|
2018-06-15 09:59:20 +02:00
|
|
|
break;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_disassociate(&sigrdataset);
|
|
|
|
}
|
2025-03-26 22:49:03 -07:00
|
|
|
|
|
|
|
if (!match) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
|
|
dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf));
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx, "No signatures for %s/%s", namebuf,
|
|
|
|
typebuf);
|
2020-06-18 17:26:46 -03:00
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(set_algorithms); i++) {
|
2018-06-15 09:59:20 +02:00
|
|
|
if (vctx->act_algorithms[i] != 0) {
|
2018-06-15 09:59:20 +02:00
|
|
|
vctx->bad_algorithms[i] = 1;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
result = ISC_R_SUCCESS;
|
|
|
|
goto done;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2025-03-21 23:32:27 -07:00
|
|
|
DNS_RDATASET_FOREACH(&sigrdataset) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
|
|
dns_rdata_rrsig_t sig;
|
2025-04-01 00:12:52 +11:00
|
|
|
dst_algorithm_t algorithm;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
dns_rdataset_current(&sigrdataset, &rdata);
|
|
|
|
result = dns_rdata_tostruct(&rdata, &sig, NULL);
|
2018-06-15 09:59:20 +02:00
|
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (rdataset->ttl != sig.originalttl) {
|
|
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
|
|
dns_rdatatype_format(rdataset->type, typebuf,
|
|
|
|
sizeof(typebuf));
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx,
|
|
|
|
"TTL mismatch for "
|
|
|
|
"%s %s keytag %u",
|
|
|
|
namebuf, typebuf, sig.keyid);
|
2018-06-15 09:59:20 +02:00
|
|
|
continue;
|
|
|
|
}
|
2025-04-01 00:12:52 +11:00
|
|
|
algorithm = dst_algorithm_fromdata(sig.algorithm, sig.signature,
|
|
|
|
sig.siglen);
|
|
|
|
if ((set_algorithms[algorithm] != 0) ||
|
|
|
|
(vctx->act_algorithms[algorithm] == 0))
|
2018-06-15 09:59:20 +02:00
|
|
|
{
|
2018-06-15 09:59:20 +02:00
|
|
|
continue;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2020-11-25 17:52:22 +11:00
|
|
|
if (goodsig(vctx, &rdata, name, dstkeys, nkeys, rdataset)) {
|
2018-06-28 13:38:39 +02:00
|
|
|
dns_rdataset_settrust(rdataset, dns_trust_secure);
|
|
|
|
dns_rdataset_settrust(&sigrdataset, dns_trust_secure);
|
2025-04-01 00:12:52 +11:00
|
|
|
set_algorithms[algorithm] = 1;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
result = ISC_R_SUCCESS;
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
if (memcmp(set_algorithms, vctx->act_algorithms,
|
2022-11-02 19:33:14 +01:00
|
|
|
sizeof(set_algorithms)) != 0)
|
|
|
|
{
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
|
|
dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf));
|
2020-06-18 17:26:46 -03:00
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(set_algorithms); i++) {
|
2018-06-15 09:59:20 +02:00
|
|
|
if ((vctx->act_algorithms[i] != 0) &&
|
2022-11-02 19:33:14 +01:00
|
|
|
(set_algorithms[i] == 0))
|
|
|
|
{
|
2025-04-01 00:12:52 +11:00
|
|
|
dst_algorithm_format(i, algbuf, sizeof(algbuf));
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx,
|
|
|
|
"No correct %s signature "
|
|
|
|
"for %s %s",
|
|
|
|
algbuf, namebuf, typebuf);
|
2018-06-15 09:59:20 +02:00
|
|
|
vctx->bad_algorithms[i] = 1;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
done:
|
|
|
|
if (dns_rdataset_isassociated(&sigrdataset)) {
|
|
|
|
dns_rdataset_disassociate(&sigrdataset);
|
|
|
|
}
|
|
|
|
dns_rdatasetiter_destroy(&rdsiter);
|
|
|
|
|
|
|
|
return result;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static isc_result_t
|
2018-06-15 09:59:20 +02:00
|
|
|
verifynode(vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node,
|
2020-11-25 17:52:22 +11:00
|
|
|
bool delegation, dst_key_t **dstkeys, size_t nkeys,
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_t *nsecset, dns_rdataset_t *nsec3paramset,
|
2018-06-15 09:59:20 +02:00
|
|
|
const dns_name_t *nextname, isc_result_t *vresult) {
|
2020-06-18 17:23:27 -03:00
|
|
|
unsigned char types[8192] = { 0 };
|
2018-06-15 09:59:20 +02:00
|
|
|
unsigned int maxtype = 0;
|
|
|
|
dns_rdatasetiter_t *rdsiter = NULL;
|
2018-08-24 21:38:06 +02:00
|
|
|
isc_result_t result, tvresult = ISC_R_UNSET;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
REQUIRE(vresult != NULL || (nsecset == NULL && nsec3paramset == NULL));
|
|
|
|
|
2022-11-16 10:47:40 +11:00
|
|
|
result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s",
|
|
|
|
isc_result_totext(result));
|
|
|
|
return result;
|
|
|
|
}
|
2020-06-18 17:26:46 -03:00
|
|
|
|
2025-03-26 22:49:03 -07:00
|
|
|
DNS_RDATASETITER_FOREACH(rdsiter) {
|
|
|
|
dns_rdataset_t rdataset = DNS_RDATASET_INIT;
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdatasetiter_current(rdsiter, &rdataset);
|
2025-03-26 22:49:03 -07:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
/*
|
|
|
|
* If we are not at a delegation then everything should be
|
|
|
|
* signed. If we are at a delegation then only the DS set
|
|
|
|
* is signed. The NS set is not signed at a delegation but
|
2020-02-21 14:12:42 -08:00
|
|
|
* its existence is recorded in the bit map. Anything else
|
2018-06-15 09:59:20 +02:00
|
|
|
* other than NSEC and DS is not signed at a delegation.
|
|
|
|
*/
|
|
|
|
if (rdataset.type != dns_rdatatype_rrsig &&
|
|
|
|
(!delegation || rdataset.type == dns_rdatatype_ds ||
|
2018-06-15 09:59:20 +02:00
|
|
|
rdataset.type == dns_rdatatype_nsec))
|
|
|
|
{
|
2020-11-25 17:52:22 +11:00
|
|
|
result = verifyset(vctx, &rdataset, name, node, dstkeys,
|
|
|
|
nkeys);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
dns_rdataset_disassociate(&rdataset);
|
|
|
|
dns_rdatasetiter_destroy(&rdsiter);
|
|
|
|
return result;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_nsec_setbit(types, rdataset.type, 1);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (rdataset.type > maxtype) {
|
2018-06-15 09:59:20 +02:00
|
|
|
maxtype = rdataset.type;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2024-01-09 12:12:33 +11:00
|
|
|
} else if (rdataset.type != dns_rdatatype_rrsig) {
|
2018-06-15 09:59:20 +02:00
|
|
|
if (rdataset.type == dns_rdatatype_ns) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_nsec_setbit(types, rdataset.type, 1);
|
2024-01-09 12:09:03 +11:00
|
|
|
if (rdataset.type > maxtype) {
|
|
|
|
maxtype = rdataset.type;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
result = check_no_rrsig(vctx, &rdataset, name, node);
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
dns_rdataset_disassociate(&rdataset);
|
|
|
|
dns_rdatasetiter_destroy(&rdsiter);
|
|
|
|
return result;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
} else {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_nsec_setbit(types, rdataset.type, 1);
|
2024-01-09 12:09:03 +11:00
|
|
|
if (rdataset.type > maxtype) {
|
|
|
|
maxtype = rdataset.type;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_disassociate(&rdataset);
|
|
|
|
}
|
|
|
|
dns_rdatasetiter_destroy(&rdsiter);
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
if (vresult == NULL) {
|
|
|
|
return ISC_R_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
*vresult = ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
if (nsecset != NULL && dns_rdataset_isassociated(nsecset)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
result = verifynsec(vctx, name, node, nextname, &tvresult);
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
*vresult = tvresult;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
if (nsec3paramset != NULL && dns_rdataset_isassociated(nsec3paramset)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
result = verifynsec3s(vctx, name, nsec3paramset, delegation,
|
2018-04-17 08:29:14 -07:00
|
|
|
false, types, maxtype, &tvresult);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
return result;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
if (*vresult == ISC_R_SUCCESS) {
|
2018-06-15 09:59:20 +02:00
|
|
|
*vresult = tvresult;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
return ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
static isc_result_t
|
2022-04-28 12:01:28 +01:00
|
|
|
is_empty(const vctx_t *vctx, dns_dbnode_t *node) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdatasetiter_t *rdsiter = NULL;
|
|
|
|
isc_result_t result;
|
|
|
|
|
2022-11-16 10:47:40 +11:00
|
|
|
result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s",
|
|
|
|
isc_result_totext(result));
|
|
|
|
return result;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_rdatasetiter_first(rdsiter);
|
|
|
|
dns_rdatasetiter_destroy(&rdsiter);
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2022-04-28 12:01:28 +01:00
|
|
|
return result;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
static isc_result_t
|
2018-06-15 09:59:20 +02:00
|
|
|
check_no_nsec(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node) {
|
2018-04-17 08:29:14 -07:00
|
|
|
bool nsec_exists = false;
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_t rdataset;
|
|
|
|
isc_result_t result;
|
|
|
|
|
|
|
|
dns_rdataset_init(&rdataset);
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
|
|
dns_rdatatype_nsec, 0, 0, &rdataset, NULL);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_NOTFOUND) {
|
|
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx, "unexpected NSEC RRset at %s",
|
|
|
|
namebuf);
|
2018-04-17 08:29:14 -07:00
|
|
|
nsec_exists = true;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
if (dns_rdataset_isassociated(&rdataset)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_disassociate(&rdataset);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
return nsec_exists ? ISC_R_FAILURE : ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
free_element(isc_mem_t *mctx, struct nsec3_chain_fixed *e) {
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
len = sizeof(*e) + e->salt_length + 2 * e->next_length;
|
|
|
|
isc_mem_put(mctx, e, len);
|
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
static void
|
|
|
|
free_element_heap(void *element, void *uap) {
|
|
|
|
struct nsec3_chain_fixed *e = (struct nsec3_chain_fixed *)element;
|
|
|
|
isc_mem_t *mctx = (isc_mem_t *)uap;
|
|
|
|
|
|
|
|
free_element(mctx, e);
|
|
|
|
}
|
|
|
|
|
2018-04-17 08:29:14 -07:00
|
|
|
static bool
|
2020-01-30 18:55:36 +11:00
|
|
|
_checknext(const vctx_t *vctx, const struct nsec3_chain_fixed *first,
|
2018-06-15 09:59:20 +02:00
|
|
|
const struct nsec3_chain_fixed *e) {
|
|
|
|
char buf[512];
|
|
|
|
const unsigned char *d1 = (const unsigned char *)(first + 1);
|
|
|
|
const unsigned char *d2 = (const unsigned char *)(e + 1);
|
|
|
|
isc_buffer_t b;
|
|
|
|
isc_region_t sr;
|
|
|
|
|
|
|
|
d1 += first->salt_length + first->next_length;
|
|
|
|
d2 += e->salt_length;
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
if (memcmp(d1, d2, first->next_length) == 0) {
|
2018-04-17 08:29:14 -07:00
|
|
|
return true;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2023-03-30 22:34:12 +02:00
|
|
|
sr.base = UNCONST(d1 - first->next_length);
|
2018-06-15 09:59:20 +02:00
|
|
|
sr.length = first->next_length;
|
|
|
|
isc_buffer_init(&b, buf, sizeof(buf));
|
|
|
|
isc_base32hex_totext(&sr, 1, "", &b);
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx, "Break in NSEC3 chain at: %.*s",
|
|
|
|
(int)isc_buffer_usedlength(&b), buf);
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2023-03-30 22:34:12 +02:00
|
|
|
sr.base = UNCONST(d1);
|
2018-06-15 09:59:20 +02:00
|
|
|
sr.length = first->next_length;
|
|
|
|
isc_buffer_init(&b, buf, sizeof(buf));
|
|
|
|
isc_base32hex_totext(&sr, 1, "", &b);
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx, "Expected: %.*s",
|
|
|
|
(int)isc_buffer_usedlength(&b), buf);
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2023-03-30 22:34:12 +02:00
|
|
|
sr.base = UNCONST(d2);
|
2018-06-15 09:59:20 +02:00
|
|
|
sr.length = first->next_length;
|
|
|
|
isc_buffer_init(&b, buf, sizeof(buf));
|
|
|
|
isc_base32hex_totext(&sr, 1, "", &b);
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx, "Found: %.*s",
|
|
|
|
(int)isc_buffer_usedlength(&b), buf);
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-04-17 08:29:14 -07:00
|
|
|
return false;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2021-10-11 13:43:12 +02:00
|
|
|
static bool
|
2020-01-30 18:55:36 +11:00
|
|
|
checknext(isc_mem_t *mctx, const vctx_t *vctx,
|
|
|
|
const struct nsec3_chain_fixed *first, struct nsec3_chain_fixed *prev,
|
|
|
|
const struct nsec3_chain_fixed *cur) {
|
|
|
|
bool result = _checknext(vctx, prev, cur);
|
|
|
|
|
|
|
|
if (prev != first) {
|
|
|
|
free_element(mctx, prev);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-10-11 13:43:12 +02:00
|
|
|
static bool
|
2020-01-30 18:55:36 +11:00
|
|
|
checklast(isc_mem_t *mctx, const vctx_t *vctx, struct nsec3_chain_fixed *first,
|
|
|
|
struct nsec3_chain_fixed *prev) {
|
|
|
|
bool result = _checknext(vctx, prev, first);
|
|
|
|
if (prev != first) {
|
|
|
|
free_element(mctx, prev);
|
|
|
|
}
|
|
|
|
free_element(mctx, first);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
static isc_result_t
|
2018-06-15 09:59:20 +02:00
|
|
|
verify_nsec3_chains(const vctx_t *vctx, isc_mem_t *mctx) {
|
2018-06-15 09:59:20 +02:00
|
|
|
isc_result_t result = ISC_R_SUCCESS;
|
|
|
|
struct nsec3_chain_fixed *e, *f = NULL;
|
|
|
|
struct nsec3_chain_fixed *first = NULL, *prev = NULL;
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
while ((e = isc_heap_element(vctx->expected_chains, 1)) != NULL) {
|
|
|
|
isc_heap_delete(vctx->expected_chains, 1);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (f == NULL) {
|
2018-06-15 09:59:20 +02:00
|
|
|
f = isc_heap_element(vctx->found_chains, 1);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
if (f != NULL) {
|
2018-06-15 09:59:20 +02:00
|
|
|
isc_heap_delete(vctx->found_chains, 1);
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Check that they match.
|
|
|
|
*/
|
2020-06-26 18:53:04 -03:00
|
|
|
if (chain_equal(e, f, chain_length(e))) {
|
2018-06-15 09:59:20 +02:00
|
|
|
free_element(mctx, f);
|
|
|
|
f = NULL;
|
|
|
|
} else {
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result == ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx, "Expected "
|
|
|
|
"and found "
|
|
|
|
"NSEC3 "
|
|
|
|
"chains not "
|
|
|
|
"equal");
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
result = ISC_R_FAILURE;
|
|
|
|
/*
|
|
|
|
* Attempt to resync found_chain.
|
|
|
|
*/
|
|
|
|
while (f != NULL && !chain_compare(e, f)) {
|
|
|
|
free_element(mctx, f);
|
2018-06-15 09:59:20 +02:00
|
|
|
f = isc_heap_element(vctx->found_chains,
|
|
|
|
1);
|
|
|
|
if (f != NULL) {
|
|
|
|
isc_heap_delete(
|
|
|
|
vctx->found_chains, 1);
|
|
|
|
}
|
2020-06-26 18:53:04 -03:00
|
|
|
if (f != NULL &&
|
|
|
|
chain_equal(e, f, chain_length(e)))
|
|
|
|
{
|
2018-06-15 09:59:20 +02:00
|
|
|
free_element(mctx, f);
|
|
|
|
f = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (result == ISC_R_SUCCESS) {
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx, "Expected and found NSEC3 "
|
|
|
|
"chains "
|
|
|
|
"not equal");
|
2018-06-15 09:59:20 +02:00
|
|
|
result = ISC_R_FAILURE;
|
|
|
|
}
|
2020-01-30 18:55:36 +11:00
|
|
|
|
|
|
|
if (first == NULL) {
|
|
|
|
prev = first = e;
|
2020-06-26 18:53:04 -03:00
|
|
|
} else if (!chain_equal(first, e, first->salt_length)) {
|
2020-01-30 18:55:36 +11:00
|
|
|
if (!checklast(mctx, vctx, first, prev)) {
|
|
|
|
result = ISC_R_FAILURE;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2020-01-30 18:55:36 +11:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
prev = first = e;
|
2020-01-30 18:55:36 +11:00
|
|
|
} else {
|
|
|
|
if (!checknext(mctx, vctx, first, prev, e)) {
|
|
|
|
result = ISC_R_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
prev = e;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
if (prev != NULL) {
|
2020-01-30 18:55:36 +11:00
|
|
|
if (!checklast(mctx, vctx, first, prev)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
result = ISC_R_FAILURE;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
do {
|
|
|
|
if (f != NULL) {
|
|
|
|
if (result == ISC_R_SUCCESS) {
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx, "Expected and found "
|
|
|
|
"NSEC3 chains not "
|
|
|
|
"equal");
|
2018-06-15 09:59:20 +02:00
|
|
|
result = ISC_R_FAILURE;
|
|
|
|
}
|
|
|
|
free_element(mctx, f);
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
f = isc_heap_element(vctx->found_chains, 1);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (f != NULL) {
|
2018-06-15 09:59:20 +02:00
|
|
|
isc_heap_delete(vctx->found_chains, 1);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
} while (f != NULL);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static isc_result_t
|
2018-06-15 09:59:20 +02:00
|
|
|
verifyemptynodes(const vctx_t *vctx, const dns_name_t *name,
|
2018-04-17 08:29:14 -07:00
|
|
|
const dns_name_t *prevname, bool isdelegation,
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_t *nsec3paramset, isc_result_t *vresult) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_namereln_t reln;
|
|
|
|
int order;
|
|
|
|
unsigned int labels, nlabels, i;
|
|
|
|
dns_name_t suffix;
|
2018-08-24 21:38:06 +02:00
|
|
|
isc_result_t result, tvresult = ISC_R_UNSET;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
*vresult = ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
reln = dns_name_fullcompare(prevname, name, &order, &labels);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (order >= 0) {
|
|
|
|
return ISC_R_SUCCESS;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
nlabels = dns_name_countlabels(name);
|
|
|
|
|
|
|
|
if (reln == dns_namereln_commonancestor ||
|
2022-11-02 19:33:14 +01:00
|
|
|
reln == dns_namereln_contains)
|
|
|
|
{
|
2025-02-21 12:09:39 +01:00
|
|
|
dns_name_init(&suffix);
|
2018-06-15 09:59:20 +02:00
|
|
|
for (i = labels + 1; i < nlabels; i++) {
|
|
|
|
dns_name_getlabelsequence(name, nlabels - i, i,
|
|
|
|
&suffix);
|
|
|
|
if (nsec3paramset != NULL &&
|
2022-11-02 19:33:14 +01:00
|
|
|
dns_rdataset_isassociated(nsec3paramset))
|
|
|
|
{
|
2018-06-15 09:59:20 +02:00
|
|
|
result = verifynsec3s(
|
|
|
|
vctx, &suffix, nsec3paramset,
|
|
|
|
isdelegation, true, NULL, 0, &tvresult);
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
if (*vresult == ISC_R_SUCCESS) {
|
|
|
|
*vresult = tvresult;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
return ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2021-10-19 11:46:37 +02:00
|
|
|
static void
|
2018-06-15 09:59:20 +02:00
|
|
|
vctx_init(vctx_t *vctx, isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db,
|
2018-06-28 13:38:39 +02:00
|
|
|
dns_dbversion_t *ver, dns_name_t *origin, dns_keytable_t *secroots) {
|
2018-06-15 09:59:20 +02:00
|
|
|
memset(vctx, 0, sizeof(*vctx));
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
vctx->mctx = mctx;
|
2018-06-15 09:59:20 +02:00
|
|
|
vctx->zone = zone;
|
2018-06-15 09:59:20 +02:00
|
|
|
vctx->db = db;
|
|
|
|
vctx->ver = ver;
|
|
|
|
vctx->origin = origin;
|
2018-06-28 13:38:39 +02:00
|
|
|
vctx->secroots = secroots;
|
2018-04-17 08:29:14 -07:00
|
|
|
vctx->goodksk = false;
|
|
|
|
vctx->goodzsk = false;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_init(&vctx->keyset);
|
|
|
|
dns_rdataset_init(&vctx->keysigs);
|
|
|
|
dns_rdataset_init(&vctx->soaset);
|
|
|
|
dns_rdataset_init(&vctx->soasigs);
|
|
|
|
dns_rdataset_init(&vctx->nsecset);
|
|
|
|
dns_rdataset_init(&vctx->nsecsigs);
|
|
|
|
dns_rdataset_init(&vctx->nsec3paramset);
|
|
|
|
dns_rdataset_init(&vctx->nsec3paramsigs);
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
vctx->expected_chains = NULL;
|
2021-10-19 11:46:37 +02:00
|
|
|
isc_heap_create(mctx, chain_compare, NULL, 1024,
|
|
|
|
&vctx->expected_chains);
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
vctx->found_chains = NULL;
|
2021-10-19 11:46:37 +02:00
|
|
|
isc_heap_create(mctx, chain_compare, NULL, 1024, &vctx->found_chains);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
vctx_destroy(vctx_t *vctx) {
|
2018-06-15 09:59:20 +02:00
|
|
|
if (dns_rdataset_isassociated(&vctx->keyset)) {
|
|
|
|
dns_rdataset_disassociate(&vctx->keyset);
|
|
|
|
}
|
|
|
|
if (dns_rdataset_isassociated(&vctx->keysigs)) {
|
|
|
|
dns_rdataset_disassociate(&vctx->keysigs);
|
|
|
|
}
|
|
|
|
if (dns_rdataset_isassociated(&vctx->soaset)) {
|
|
|
|
dns_rdataset_disassociate(&vctx->soaset);
|
|
|
|
}
|
|
|
|
if (dns_rdataset_isassociated(&vctx->soasigs)) {
|
|
|
|
dns_rdataset_disassociate(&vctx->soasigs);
|
|
|
|
}
|
|
|
|
if (dns_rdataset_isassociated(&vctx->nsecset)) {
|
|
|
|
dns_rdataset_disassociate(&vctx->nsecset);
|
|
|
|
}
|
|
|
|
if (dns_rdataset_isassociated(&vctx->nsecsigs)) {
|
|
|
|
dns_rdataset_disassociate(&vctx->nsecsigs);
|
|
|
|
}
|
|
|
|
if (dns_rdataset_isassociated(&vctx->nsec3paramset)) {
|
|
|
|
dns_rdataset_disassociate(&vctx->nsec3paramset);
|
|
|
|
}
|
|
|
|
if (dns_rdataset_isassociated(&vctx->nsec3paramsigs)) {
|
|
|
|
dns_rdataset_disassociate(&vctx->nsec3paramsigs);
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
isc_heap_foreach(vctx->expected_chains, free_element_heap, vctx->mctx);
|
2018-06-15 09:59:20 +02:00
|
|
|
isc_heap_destroy(&vctx->expected_chains);
|
2018-06-15 09:59:20 +02:00
|
|
|
isc_heap_foreach(vctx->found_chains, free_element_heap, vctx->mctx);
|
2018-06-15 09:59:20 +02:00
|
|
|
isc_heap_destroy(&vctx->found_chains);
|
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
static isc_result_t
|
2018-06-15 09:59:20 +02:00
|
|
|
check_apex_rrsets(vctx_t *vctx) {
|
|
|
|
dns_dbnode_t *node = NULL;
|
|
|
|
isc_result_t result;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-04-17 08:29:14 -07:00
|
|
|
result = dns_db_findnode(vctx->db, vctx->origin, false, &node);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx,
|
|
|
|
"failed to find the zone's origin: %s",
|
|
|
|
isc_result_totext(result));
|
|
|
|
return result;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
|
|
dns_rdatatype_dnskey, 0, 0, &vctx->keyset,
|
|
|
|
&vctx->keysigs);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx, "Zone contains no DNSSEC keys");
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
|
|
dns_rdatatype_soa, 0, 0, &vctx->soaset,
|
|
|
|
&vctx->soasigs);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx, "Zone contains no SOA record");
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_db_findrdataset(vctx->db, node, vctx->ver,
|
|
|
|
dns_rdatatype_nsec, 0, 0, &vctx->nsecset,
|
|
|
|
&vctx->nsecsigs);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
|
|
|
|
zoneverify_log_error(vctx, "NSEC lookup failed");
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_db_findrdataset(
|
2018-06-15 09:59:20 +02:00
|
|
|
vctx->db, node, vctx->ver, dns_rdatatype_nsec3param, 0, 0,
|
2018-06-15 09:59:20 +02:00
|
|
|
&vctx->nsec3paramset, &vctx->nsec3paramsigs);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
|
|
|
|
zoneverify_log_error(vctx, "NSEC3PARAM lookup failed");
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
if (!dns_rdataset_isassociated(&vctx->keysigs)) {
|
|
|
|
zoneverify_log_error(vctx, "DNSKEY is not signed "
|
|
|
|
"(keys offline or inactive?)");
|
|
|
|
result = ISC_R_FAILURE;
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
if (!dns_rdataset_isassociated(&vctx->soasigs)) {
|
|
|
|
zoneverify_log_error(vctx, "SOA is not signed "
|
|
|
|
"(keys offline or inactive?)");
|
|
|
|
result = ISC_R_FAILURE;
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
if (dns_rdataset_isassociated(&vctx->nsecset) &&
|
|
|
|
!dns_rdataset_isassociated(&vctx->nsecsigs))
|
|
|
|
{
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx, "NSEC is not signed "
|
|
|
|
"(keys offline or inactive?)");
|
|
|
|
result = ISC_R_FAILURE;
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
if (dns_rdataset_isassociated(&vctx->nsec3paramset) &&
|
|
|
|
!dns_rdataset_isassociated(&vctx->nsec3paramsigs))
|
|
|
|
{
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx, "NSEC3PARAM is not signed "
|
|
|
|
"(keys offline or inactive?)");
|
|
|
|
result = ISC_R_FAILURE;
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
if (!dns_rdataset_isassociated(&vctx->nsecset) &&
|
|
|
|
!dns_rdataset_isassociated(&vctx->nsec3paramset))
|
|
|
|
{
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx, "No valid NSEC/NSEC3 chain for "
|
|
|
|
"testing");
|
|
|
|
result = ISC_R_FAILURE;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
result = ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
done:
|
Decouple database and node lifetimes by adding node-specific vtables
All databases in the codebase follow the same structure: a database is
an associative container from DNS names to nodes, and each node is an
associative container from RR types to RR data.
Each database implementation (qpzone, qpcache, sdlz, builtin, dyndb) has
its own corresponding node type (qpznode, qpcnode, etc). However, some
code needs to work with nodes generically regardless of their specific
type - for example, to acquire locks, manage references, or
register/unregister slabs from the heap.
Currently, these generic node operations are implemented as methods in
the database vtable, which creates problematic coupling between database
and node lifetimes. If a node outlives its parent database, the node
destructor will destroy all RR data, and each RR data destructor will
try to unregister from heaps by calling a virtual function from the
database vtable. Since the database was already freed, this causes a
crash.
This commit breaks the coupling by standardizing the layout of all
database nodes, adding a dedicated vtable for node operations, and
moving node-specific methods from the database vtable to the node
vtable.
2025-06-05 11:51:29 +02:00
|
|
|
dns_db_detachnode(&node);
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
return result;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
/*%
|
|
|
|
* Update 'vctx' tables tracking active and standby key algorithms used in the
|
|
|
|
* verified zone based on the signatures made using 'dnskey' (prepared from
|
|
|
|
* 'rdata') found at zone apex. Set 'vctx->goodksk' or 'vctx->goodzsk' to true
|
2018-06-28 13:38:39 +02:00
|
|
|
* if 'dnskey' correctly signs the DNSKEY RRset at zone apex and either
|
|
|
|
* 'vctx->secroots' is NULL or 'dnskey' is present in 'vctx->secroots'.
|
2018-06-15 09:59:20 +02:00
|
|
|
*
|
|
|
|
* The variables to update are chosen based on 'is_ksk', which is true when
|
|
|
|
* 'dnskey' is a KSK and false otherwise.
|
|
|
|
*/
|
2019-10-24 13:55:56 +02:00
|
|
|
static void
|
2018-06-15 09:59:20 +02:00
|
|
|
check_dnskey_sigs(vctx_t *vctx, const dns_rdata_dnskey_t *dnskey,
|
2019-11-17 12:14:40 -08:00
|
|
|
dns_rdata_t *keyrdata, bool is_ksk) {
|
2019-09-19 17:43:14 -07:00
|
|
|
unsigned char *active_keys = NULL, *standby_keys = NULL;
|
2018-06-28 13:38:39 +02:00
|
|
|
dns_keynode_t *keynode = NULL;
|
2019-09-19 17:43:14 -07:00
|
|
|
bool *goodkey = NULL;
|
2018-06-28 13:38:39 +02:00
|
|
|
dst_key_t *key = NULL;
|
|
|
|
isc_result_t result;
|
2020-06-10 17:07:52 +10:00
|
|
|
dns_rdataset_t dsset;
|
2025-04-01 00:12:52 +11:00
|
|
|
dst_algorithm_t algorithm;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
active_keys = (is_ksk ? vctx->ksk_algorithms : vctx->zsk_algorithms);
|
|
|
|
standby_keys = (is_ksk ? vctx->standby_ksk : vctx->standby_zsk);
|
|
|
|
goodkey = (is_ksk ? &vctx->goodksk : &vctx->goodzsk);
|
2025-04-01 00:12:52 +11:00
|
|
|
algorithm = dst_algorithm_fromdata(dnskey->algorithm, dnskey->data,
|
|
|
|
dnskey->datalen);
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2019-11-17 12:14:40 -08:00
|
|
|
/*
|
|
|
|
* First, does this key sign the DNSKEY rrset?
|
|
|
|
*/
|
|
|
|
if (!dns_dnssec_selfsigns(keyrdata, vctx->origin, &vctx->keyset,
|
2018-04-17 08:29:14 -07:00
|
|
|
&vctx->keysigs, false, vctx->mctx))
|
|
|
|
{
|
2019-10-24 13:55:56 +02:00
|
|
|
if (!is_ksk &&
|
2019-11-17 12:14:40 -08:00
|
|
|
dns_dnssec_signs(keyrdata, vctx->origin, &vctx->soaset,
|
2019-10-24 13:55:56 +02:00
|
|
|
&vctx->soasigs, false, vctx->mctx))
|
|
|
|
{
|
2025-04-01 00:12:52 +11:00
|
|
|
if (active_keys[algorithm] != DNS_KEYALG_MAX) {
|
|
|
|
active_keys[algorithm]++;
|
2019-10-24 13:55:56 +02:00
|
|
|
}
|
|
|
|
} else {
|
2025-04-01 00:12:52 +11:00
|
|
|
if (standby_keys[algorithm] != DNS_KEYALG_MAX) {
|
|
|
|
standby_keys[algorithm]++;
|
2019-10-24 13:55:56 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2019-10-24 13:55:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-04-01 00:12:52 +11:00
|
|
|
if (active_keys[algorithm] != DNS_KEYALG_MAX) {
|
|
|
|
active_keys[algorithm]++;
|
2018-06-28 13:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If a trust anchor table was not supplied, a correctly self-signed
|
|
|
|
* DNSKEY RRset is good enough.
|
|
|
|
*/
|
|
|
|
if (vctx->secroots == NULL) {
|
2018-04-17 08:29:14 -07:00
|
|
|
*goodkey = true;
|
2019-10-24 13:55:56 +02:00
|
|
|
return;
|
2018-06-28 13:38:39 +02:00
|
|
|
}
|
|
|
|
|
2019-11-17 12:14:40 -08:00
|
|
|
/*
|
|
|
|
* Convert the supplied key rdata to dst_key_t. (If this
|
|
|
|
* fails we can't go further.)
|
|
|
|
*/
|
|
|
|
result = dns_dnssec_keyfromrdata(vctx->origin, keyrdata, vctx->mctx,
|
|
|
|
&key);
|
|
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
|
|
|
2018-06-28 13:38:39 +02:00
|
|
|
/*
|
|
|
|
* Look up the supplied key in the trust anchor table.
|
2019-11-17 12:14:40 -08:00
|
|
|
* If we don't find an exact match, or if the keynode data
|
|
|
|
* is NULL, then we have neither a DNSKEY nor a DS format
|
|
|
|
* trust anchor, and can give up.
|
2018-06-28 13:38:39 +02:00
|
|
|
*/
|
2019-11-17 12:14:40 -08:00
|
|
|
result = dns_keytable_find(vctx->secroots, vctx->origin, &keynode);
|
2018-06-28 13:38:39 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
2019-11-17 12:14:40 -08:00
|
|
|
/* No such trust anchor */
|
2019-10-24 13:55:56 +02:00
|
|
|
goto cleanup;
|
2018-06-28 13:38:39 +02:00
|
|
|
}
|
2019-09-19 17:43:14 -07:00
|
|
|
|
2019-11-17 12:14:40 -08:00
|
|
|
/*
|
|
|
|
* If the keynode has any DS format trust anchors, that means
|
|
|
|
* it doesn't have any DNSKEY ones. So, we can check for a DS
|
|
|
|
* match and then stop.
|
|
|
|
*/
|
2020-06-10 17:07:52 +10:00
|
|
|
dns_rdataset_init(&dsset);
|
|
|
|
if (dns_keynode_dsset(keynode, &dsset)) {
|
2025-03-21 23:32:27 -07:00
|
|
|
DNS_RDATASET_FOREACH(&dsset) {
|
2019-11-17 12:14:40 -08:00
|
|
|
dns_rdata_t dsrdata = DNS_RDATA_INIT;
|
|
|
|
dns_rdata_t newdsrdata = DNS_RDATA_INIT;
|
|
|
|
unsigned char buf[DNS_DS_BUFFERSIZE];
|
|
|
|
dns_rdata_ds_t ds;
|
|
|
|
|
|
|
|
dns_rdata_reset(&dsrdata);
|
2020-06-10 17:07:52 +10:00
|
|
|
dns_rdataset_current(&dsset, &dsrdata);
|
2019-11-17 12:14:40 -08:00
|
|
|
result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
|
|
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
|
|
|
|
|
|
if (ds.key_tag != dst_key_id(key) ||
|
2025-04-01 00:12:52 +11:00
|
|
|
ds.algorithm !=
|
|
|
|
dst_algorithm_tosecalg(dst_key_alg(key)))
|
2022-11-02 19:33:14 +01:00
|
|
|
{
|
2019-11-17 12:14:40 -08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
result = dns_ds_buildrdata(vctx->origin, keyrdata,
|
|
|
|
ds.digest_type, buf,
|
2025-05-21 10:13:04 +10:00
|
|
|
sizeof(buf), &newdsrdata);
|
2019-11-17 12:14:40 -08:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dns_rdata_compare(&dsrdata, &newdsrdata) == 0) {
|
|
|
|
dns_rdataset_settrust(&vctx->keyset,
|
|
|
|
dns_trust_secure);
|
|
|
|
dns_rdataset_settrust(&vctx->keysigs,
|
|
|
|
dns_trust_secure);
|
|
|
|
*goodkey = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2020-06-10 17:07:52 +10:00
|
|
|
dns_rdataset_disassociate(&dsset);
|
2019-11-17 12:14:40 -08:00
|
|
|
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2018-06-28 13:38:39 +02:00
|
|
|
|
2019-09-19 17:43:14 -07:00
|
|
|
cleanup:
|
|
|
|
if (keynode != NULL) {
|
2023-04-15 14:49:45 -07:00
|
|
|
dns_keynode_detach(&keynode);
|
2019-09-19 17:43:14 -07:00
|
|
|
}
|
|
|
|
if (key != NULL) {
|
|
|
|
dst_key_free(&key);
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
/*%
|
|
|
|
* Check that the DNSKEY RR has at least one self signing KSK and one ZSK per
|
|
|
|
* algorithm in it (or, if -x was used, one self-signing KSK).
|
|
|
|
*/
|
2018-06-15 09:59:20 +02:00
|
|
|
static isc_result_t
|
2018-06-15 09:59:20 +02:00
|
|
|
check_dnskey(vctx_t *vctx) {
|
|
|
|
dns_rdata_dnskey_t dnskey;
|
|
|
|
isc_result_t result;
|
2018-04-17 08:29:14 -07:00
|
|
|
bool is_ksk;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2025-03-21 23:32:27 -07:00
|
|
|
DNS_RDATASET_FOREACH(&vctx->keyset) {
|
|
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_rdataset_current(&vctx->keyset, &rdata);
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_rdata_tostruct(&rdata, &dnskey, NULL);
|
2018-06-15 09:59:20 +02:00
|
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
2018-10-11 11:57:57 +02:00
|
|
|
is_ksk = ((dnskey.flags & DNS_KEYFLAG_KSK) != 0);
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2020-06-27 16:37:23 -03:00
|
|
|
if ((dnskey.flags & DNS_KEYOWNER_ZONE) != 0 &&
|
|
|
|
(dnskey.flags & DNS_KEYFLAG_REVOKE) != 0)
|
|
|
|
{
|
2025-04-01 00:12:52 +11:00
|
|
|
dst_algorithm_t algorithm;
|
2018-06-15 09:59:20 +02:00
|
|
|
if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 &&
|
2018-06-15 09:59:20 +02:00
|
|
|
!dns_dnssec_selfsigns(&rdata, vctx->origin,
|
2018-04-17 08:29:14 -07:00
|
|
|
&vctx->keyset, &vctx->keysigs,
|
2018-06-15 09:59:20 +02:00
|
|
|
false, vctx->mctx))
|
|
|
|
{
|
2018-06-15 09:59:20 +02:00
|
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
|
|
char buffer[1024];
|
|
|
|
isc_buffer_t buf;
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_name_format(vctx->origin, namebuf,
|
2018-06-15 09:59:20 +02:00
|
|
|
sizeof(namebuf));
|
|
|
|
isc_buffer_init(&buf, buffer, sizeof(buffer));
|
|
|
|
result = dns_rdata_totext(&rdata, NULL, &buf);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(
|
|
|
|
vctx, "dns_rdata_totext: %s",
|
|
|
|
isc_result_totext(result));
|
|
|
|
return ISC_R_FAILURE;
|
|
|
|
}
|
|
|
|
zoneverify_log_error(
|
|
|
|
vctx,
|
|
|
|
"revoked KSK is not self signed:\n"
|
|
|
|
"%s DNSKEY %.*s",
|
|
|
|
namebuf,
|
|
|
|
(int)isc_buffer_usedlength(&buf),
|
|
|
|
buffer);
|
|
|
|
return ISC_R_FAILURE;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2025-04-01 00:12:52 +11:00
|
|
|
algorithm = dst_algorithm_fromdata(
|
|
|
|
dnskey.algorithm, dnskey.data, dnskey.datalen);
|
2018-06-15 09:59:20 +02:00
|
|
|
if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 &&
|
2025-04-01 00:12:52 +11:00
|
|
|
vctx->revoked_ksk[algorithm] != DNS_KEYALG_MAX)
|
2018-06-15 09:59:20 +02:00
|
|
|
{
|
2025-04-01 00:12:52 +11:00
|
|
|
vctx->revoked_ksk[algorithm]++;
|
2018-06-15 09:59:20 +02:00
|
|
|
} else if ((dnskey.flags & DNS_KEYFLAG_KSK) == 0 &&
|
2025-04-01 00:12:52 +11:00
|
|
|
vctx->revoked_zsk[algorithm] !=
|
2020-06-27 16:59:41 -03:00
|
|
|
DNS_KEYALG_MAX)
|
2018-06-15 09:59:20 +02:00
|
|
|
{
|
2025-04-01 00:12:52 +11:00
|
|
|
vctx->revoked_zsk[algorithm]++;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
} else {
|
2018-06-15 09:59:20 +02:00
|
|
|
check_dnskey_sigs(vctx, &dnskey, &rdata, is_ksk);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
dns_rdata_freestruct(&dnskey);
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
return ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
static void
|
2018-04-17 08:29:14 -07:00
|
|
|
determine_active_algorithms(vctx_t *vctx, bool ignore_kskflag,
|
2019-07-21 08:07:20 -04:00
|
|
|
bool keyset_kskonly,
|
|
|
|
void (*report)(const char *, ...)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
char algbuf[DNS_SECALG_FORMATSIZE];
|
|
|
|
|
2019-07-21 08:07:20 -04:00
|
|
|
report("Verifying the zone using the following algorithms:");
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2020-06-18 17:26:46 -03:00
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(vctx->act_algorithms); i++) {
|
2018-06-15 09:59:20 +02:00
|
|
|
if (ignore_kskflag) {
|
2018-06-15 09:59:20 +02:00
|
|
|
vctx->act_algorithms[i] = (vctx->ksk_algorithms[i] !=
|
|
|
|
0 ||
|
|
|
|
vctx->zsk_algorithms[i] != 0)
|
|
|
|
? 1
|
|
|
|
: 0;
|
2018-06-15 09:59:20 +02:00
|
|
|
} else {
|
2018-06-15 09:59:20 +02:00
|
|
|
vctx->act_algorithms[i] = vctx->ksk_algorithms[i] != 0
|
|
|
|
? 1
|
|
|
|
: 0;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
if (vctx->act_algorithms[i] != 0) {
|
2025-04-01 00:12:52 +11:00
|
|
|
dst_algorithm_format(i, algbuf, sizeof(algbuf));
|
2021-01-05 12:06:23 +01:00
|
|
|
report("- %s", algbuf);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ignore_kskflag || keyset_kskonly) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-06-18 17:26:46 -03:00
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(vctx->ksk_algorithms); i++) {
|
2018-06-15 09:59:20 +02:00
|
|
|
/*
|
|
|
|
* The counts should both be zero or both be non-zero. Mark
|
|
|
|
* the algorithm as bad if this is not met.
|
|
|
|
*/
|
|
|
|
if ((vctx->ksk_algorithms[i] != 0) ==
|
2022-11-02 19:33:14 +01:00
|
|
|
(vctx->zsk_algorithms[i] != 0))
|
|
|
|
{
|
2018-06-15 09:59:20 +02:00
|
|
|
continue;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2025-04-01 00:12:52 +11:00
|
|
|
dst_algorithm_format(i, algbuf, sizeof(algbuf));
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx, "Missing %s for algorithm %s",
|
|
|
|
(vctx->ksk_algorithms[i] != 0) ? "ZSK"
|
|
|
|
: "self-"
|
|
|
|
"signed "
|
|
|
|
"KSK",
|
|
|
|
algbuf);
|
|
|
|
vctx->bad_algorithms[i] = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
/*%
|
|
|
|
* Check that all the records not yet verified were signed by keys that are
|
|
|
|
* present in the DNSKEY RRset.
|
|
|
|
*/
|
2018-06-15 09:59:20 +02:00
|
|
|
static isc_result_t
|
2018-06-15 09:59:20 +02:00
|
|
|
verify_nodes(vctx_t *vctx, isc_result_t *vresult) {
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_fixedname_t fname, fnextname, fprevname, fzonecut;
|
|
|
|
dns_name_t *name, *nextname, *prevname, *zonecut;
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_dbnode_t *node = NULL, *nextnode;
|
|
|
|
dns_dbiterator_t *dbiter = NULL;
|
2020-11-25 17:52:22 +11:00
|
|
|
dst_key_t **dstkeys;
|
|
|
|
size_t count, nkeys = 0;
|
2018-04-17 08:29:14 -07:00
|
|
|
bool done = false;
|
2018-08-24 21:38:06 +02:00
|
|
|
isc_result_t tvresult = ISC_R_UNSET;
|
2018-06-15 09:59:20 +02:00
|
|
|
isc_result_t result;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
name = dns_fixedname_initname(&fname);
|
|
|
|
nextname = dns_fixedname_initname(&fnextname);
|
|
|
|
dns_fixedname_init(&fprevname);
|
|
|
|
prevname = NULL;
|
|
|
|
dns_fixedname_init(&fzonecut);
|
|
|
|
zonecut = NULL;
|
|
|
|
|
2020-11-25 17:52:22 +11:00
|
|
|
count = dns_rdataset_count(&vctx->keyset);
|
2023-08-23 08:56:31 +02:00
|
|
|
dstkeys = isc_mem_cget(vctx->mctx, count, sizeof(*dstkeys));
|
2020-11-25 17:52:22 +11:00
|
|
|
|
2025-03-21 23:32:27 -07:00
|
|
|
DNS_RDATASET_FOREACH(&vctx->keyset) {
|
2020-11-25 17:52:22 +11:00
|
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
|
|
dns_rdataset_current(&vctx->keyset, &rdata);
|
|
|
|
dstkeys[nkeys] = NULL;
|
|
|
|
result = dns_dnssec_keyfromrdata(vctx->origin, &rdata,
|
|
|
|
vctx->mctx, &dstkeys[nkeys]);
|
|
|
|
if (result == ISC_R_SUCCESS) {
|
|
|
|
nkeys++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_db_createiterator(vctx->db, DNS_DB_NONSEC3, &dbiter);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx, "dns_db_createiterator(): %s",
|
|
|
|
isc_result_totext(result));
|
2020-11-25 17:52:22 +11:00
|
|
|
goto done;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
result = dns_dbiterator_first(dbiter);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx, "dns_dbiterator_first(): %s",
|
|
|
|
isc_result_totext(result));
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
while (!done) {
|
2018-04-17 08:29:14 -07:00
|
|
|
bool isdelegation = false;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
result = dns_dbiterator_current(dbiter, &node, name);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
|
|
|
|
zoneverify_log_error(vctx,
|
|
|
|
"dns_dbiterator_current(): %s",
|
|
|
|
isc_result_totext(result));
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
if (!dns_name_issubdomain(name, vctx->origin)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
result = check_no_nsec(vctx, name, node);
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
Decouple database and node lifetimes by adding node-specific vtables
All databases in the codebase follow the same structure: a database is
an associative container from DNS names to nodes, and each node is an
associative container from RR types to RR data.
Each database implementation (qpzone, qpcache, sdlz, builtin, dyndb) has
its own corresponding node type (qpznode, qpcnode, etc). However, some
code needs to work with nodes generically regardless of their specific
type - for example, to acquire locks, manage references, or
register/unregister slabs from the heap.
Currently, these generic node operations are implemented as methods in
the database vtable, which creates problematic coupling between database
and node lifetimes. If a node outlives its parent database, the node
destructor will destroy all RR data, and each RR data destructor will
try to unregister from heaps by calling a virtual function from the
database vtable. Since the database was already freed, this causes a
crash.
This commit breaks the coupling by standardizing the layout of all
database nodes, adding a dedicated vtable for node operations, and
moving node-specific methods from the database vtable to the node
vtable.
2025-06-05 11:51:29 +02:00
|
|
|
dns_db_detachnode(&node);
|
2018-06-15 09:59:20 +02:00
|
|
|
goto done;
|
|
|
|
}
|
Decouple database and node lifetimes by adding node-specific vtables
All databases in the codebase follow the same structure: a database is
an associative container from DNS names to nodes, and each node is an
associative container from RR types to RR data.
Each database implementation (qpzone, qpcache, sdlz, builtin, dyndb) has
its own corresponding node type (qpznode, qpcnode, etc). However, some
code needs to work with nodes generically regardless of their specific
type - for example, to acquire locks, manage references, or
register/unregister slabs from the heap.
Currently, these generic node operations are implemented as methods in
the database vtable, which creates problematic coupling between database
and node lifetimes. If a node outlives its parent database, the node
destructor will destroy all RR data, and each RR data destructor will
try to unregister from heaps by calling a virtual function from the
database vtable. Since the database was already freed, this causes a
crash.
This commit breaks the coupling by standardizing the layout of all
database nodes, adding a dedicated vtable for node operations, and
moving node-specific methods from the database vtable to the node
vtable.
2025-06-05 11:51:29 +02:00
|
|
|
dns_db_detachnode(&node);
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_dbiterator_next(dbiter);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result == ISC_R_NOMORE) {
|
2018-04-17 08:29:14 -07:00
|
|
|
done = true;
|
2018-06-15 09:59:20 +02:00
|
|
|
} else if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx,
|
|
|
|
"dns_dbiterator_next(): "
|
|
|
|
"%s",
|
|
|
|
isc_result_totext(result));
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
continue;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
if (is_delegation(vctx, name, node, NULL)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
zonecut = dns_fixedname_name(&fzonecut);
|
2021-05-21 17:20:44 -07:00
|
|
|
dns_name_copy(name, zonecut);
|
2018-04-17 08:29:14 -07:00
|
|
|
isdelegation = true;
|
2018-06-15 09:59:20 +02:00
|
|
|
} else if (has_dname(vctx, node)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
zonecut = dns_fixedname_name(&fzonecut);
|
2021-05-21 17:20:44 -07:00
|
|
|
dns_name_copy(name, zonecut);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
nextnode = NULL;
|
|
|
|
result = dns_dbiterator_next(dbiter);
|
|
|
|
while (result == ISC_R_SUCCESS) {
|
|
|
|
result = dns_dbiterator_current(dbiter, &nextnode,
|
|
|
|
nextname);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS &&
|
2022-11-02 19:33:14 +01:00
|
|
|
result != DNS_R_NEWORIGIN)
|
|
|
|
{
|
2018-06-15 09:59:20 +02:00
|
|
|
zoneverify_log_error(vctx,
|
|
|
|
"dns_dbiterator_current():"
|
|
|
|
" %s",
|
|
|
|
isc_result_totext(result));
|
Decouple database and node lifetimes by adding node-specific vtables
All databases in the codebase follow the same structure: a database is
an associative container from DNS names to nodes, and each node is an
associative container from RR types to RR data.
Each database implementation (qpzone, qpcache, sdlz, builtin, dyndb) has
its own corresponding node type (qpznode, qpcnode, etc). However, some
code needs to work with nodes generically regardless of their specific
type - for example, to acquire locks, manage references, or
register/unregister slabs from the heap.
Currently, these generic node operations are implemented as methods in
the database vtable, which creates problematic coupling between database
and node lifetimes. If a node outlives its parent database, the node
destructor will destroy all RR data, and each RR data destructor will
try to unregister from heaps by calling a virtual function from the
database vtable. Since the database was already freed, this causes a
crash.
This commit breaks the coupling by standardizing the layout of all
database nodes, adding a dedicated vtable for node operations, and
moving node-specific methods from the database vtable to the node
vtable.
2025-06-05 11:51:29 +02:00
|
|
|
dns_db_detachnode(&node);
|
2018-06-15 09:59:20 +02:00
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
if (!dns_name_issubdomain(nextname, vctx->origin) ||
|
2018-06-15 09:59:20 +02:00
|
|
|
(zonecut != NULL &&
|
|
|
|
dns_name_issubdomain(nextname, zonecut)))
|
|
|
|
{
|
2018-06-15 09:59:20 +02:00
|
|
|
result = check_no_nsec(vctx, nextname,
|
|
|
|
nextnode);
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
Decouple database and node lifetimes by adding node-specific vtables
All databases in the codebase follow the same structure: a database is
an associative container from DNS names to nodes, and each node is an
associative container from RR types to RR data.
Each database implementation (qpzone, qpcache, sdlz, builtin, dyndb) has
its own corresponding node type (qpznode, qpcnode, etc). However, some
code needs to work with nodes generically regardless of their specific
type - for example, to acquire locks, manage references, or
register/unregister slabs from the heap.
Currently, these generic node operations are implemented as methods in
the database vtable, which creates problematic coupling between database
and node lifetimes. If a node outlives its parent database, the node
destructor will destroy all RR data, and each RR data destructor will
try to unregister from heaps by calling a virtual function from the
database vtable. Since the database was already freed, this causes a
crash.
This commit breaks the coupling by standardizing the layout of all
database nodes, adding a dedicated vtable for node operations, and
moving node-specific methods from the database vtable to the node
vtable.
2025-06-05 11:51:29 +02:00
|
|
|
dns_db_detachnode(&node);
|
|
|
|
dns_db_detachnode(&nextnode);
|
2018-06-15 09:59:20 +02:00
|
|
|
goto done;
|
|
|
|
}
|
Decouple database and node lifetimes by adding node-specific vtables
All databases in the codebase follow the same structure: a database is
an associative container from DNS names to nodes, and each node is an
associative container from RR types to RR data.
Each database implementation (qpzone, qpcache, sdlz, builtin, dyndb) has
its own corresponding node type (qpznode, qpcnode, etc). However, some
code needs to work with nodes generically regardless of their specific
type - for example, to acquire locks, manage references, or
register/unregister slabs from the heap.
Currently, these generic node operations are implemented as methods in
the database vtable, which creates problematic coupling between database
and node lifetimes. If a node outlives its parent database, the node
destructor will destroy all RR data, and each RR data destructor will
try to unregister from heaps by calling a virtual function from the
database vtable. Since the database was already freed, this causes a
crash.
This commit breaks the coupling by standardizing the layout of all
database nodes, adding a dedicated vtable for node operations, and
moving node-specific methods from the database vtable to the node
vtable.
2025-06-05 11:51:29 +02:00
|
|
|
dns_db_detachnode(&nextnode);
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_dbiterator_next(dbiter);
|
|
|
|
continue;
|
|
|
|
}
|
2022-04-28 12:01:28 +01:00
|
|
|
result = is_empty(vctx, nextnode);
|
Decouple database and node lifetimes by adding node-specific vtables
All databases in the codebase follow the same structure: a database is
an associative container from DNS names to nodes, and each node is an
associative container from RR types to RR data.
Each database implementation (qpzone, qpcache, sdlz, builtin, dyndb) has
its own corresponding node type (qpznode, qpcnode, etc). However, some
code needs to work with nodes generically regardless of their specific
type - for example, to acquire locks, manage references, or
register/unregister slabs from the heap.
Currently, these generic node operations are implemented as methods in
the database vtable, which creates problematic coupling between database
and node lifetimes. If a node outlives its parent database, the node
destructor will destroy all RR data, and each RR data destructor will
try to unregister from heaps by calling a virtual function from the
database vtable. Since the database was already freed, this causes a
crash.
This commit breaks the coupling by standardizing the layout of all
database nodes, adding a dedicated vtable for node operations, and
moving node-specific methods from the database vtable to the node
vtable.
2025-06-05 11:51:29 +02:00
|
|
|
dns_db_detachnode(&nextnode);
|
2022-04-28 12:01:28 +01:00
|
|
|
switch (result) {
|
|
|
|
case ISC_R_SUCCESS:
|
|
|
|
break;
|
|
|
|
case ISC_R_NOMORE:
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_dbiterator_next(dbiter);
|
|
|
|
continue;
|
2022-04-28 12:01:28 +01:00
|
|
|
default:
|
Decouple database and node lifetimes by adding node-specific vtables
All databases in the codebase follow the same structure: a database is
an associative container from DNS names to nodes, and each node is an
associative container from RR types to RR data.
Each database implementation (qpzone, qpcache, sdlz, builtin, dyndb) has
its own corresponding node type (qpznode, qpcnode, etc). However, some
code needs to work with nodes generically regardless of their specific
type - for example, to acquire locks, manage references, or
register/unregister slabs from the heap.
Currently, these generic node operations are implemented as methods in
the database vtable, which creates problematic coupling between database
and node lifetimes. If a node outlives its parent database, the node
destructor will destroy all RR data, and each RR data destructor will
try to unregister from heaps by calling a virtual function from the
database vtable. Since the database was already freed, this causes a
crash.
This commit breaks the coupling by standardizing the layout of all
database nodes, adding a dedicated vtable for node operations, and
moving node-specific methods from the database vtable to the node
vtable.
2025-06-05 11:51:29 +02:00
|
|
|
dns_db_detachnode(&node);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (result == ISC_R_NOMORE) {
|
2018-04-17 08:29:14 -07:00
|
|
|
done = true;
|
2018-06-15 09:59:20 +02:00
|
|
|
nextname = vctx->origin;
|
2018-06-15 09:59:20 +02:00
|
|
|
} else if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx,
|
|
|
|
"iterating through the database "
|
|
|
|
"failed: %s",
|
|
|
|
isc_result_totext(result));
|
Decouple database and node lifetimes by adding node-specific vtables
All databases in the codebase follow the same structure: a database is
an associative container from DNS names to nodes, and each node is an
associative container from RR types to RR data.
Each database implementation (qpzone, qpcache, sdlz, builtin, dyndb) has
its own corresponding node type (qpznode, qpcnode, etc). However, some
code needs to work with nodes generically regardless of their specific
type - for example, to acquire locks, manage references, or
register/unregister slabs from the heap.
Currently, these generic node operations are implemented as methods in
the database vtable, which creates problematic coupling between database
and node lifetimes. If a node outlives its parent database, the node
destructor will destroy all RR data, and each RR data destructor will
try to unregister from heaps by calling a virtual function from the
database vtable. Since the database was already freed, this causes a
crash.
This commit breaks the coupling by standardizing the layout of all
database nodes, adding a dedicated vtable for node operations, and
moving node-specific methods from the database vtable to the node
vtable.
2025-06-05 11:51:29 +02:00
|
|
|
dns_db_detachnode(&node);
|
2018-06-15 09:59:20 +02:00
|
|
|
goto done;
|
|
|
|
}
|
2020-11-25 17:52:22 +11:00
|
|
|
result = verifynode(vctx, name, node, isdelegation, dstkeys,
|
|
|
|
nkeys, &vctx->nsecset, &vctx->nsec3paramset,
|
|
|
|
nextname, &tvresult);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
Decouple database and node lifetimes by adding node-specific vtables
All databases in the codebase follow the same structure: a database is
an associative container from DNS names to nodes, and each node is an
associative container from RR types to RR data.
Each database implementation (qpzone, qpcache, sdlz, builtin, dyndb) has
its own corresponding node type (qpznode, qpcnode, etc). However, some
code needs to work with nodes generically regardless of their specific
type - for example, to acquire locks, manage references, or
register/unregister slabs from the heap.
Currently, these generic node operations are implemented as methods in
the database vtable, which creates problematic coupling between database
and node lifetimes. If a node outlives its parent database, the node
destructor will destroy all RR data, and each RR data destructor will
try to unregister from heaps by calling a virtual function from the
database vtable. Since the database was already freed, this causes a
crash.
This commit breaks the coupling by standardizing the layout of all
database nodes, adding a dedicated vtable for node operations, and
moving node-specific methods from the database vtable to the node
vtable.
2025-06-05 11:51:29 +02:00
|
|
|
dns_db_detachnode(&node);
|
2018-06-15 09:59:20 +02:00
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
if (*vresult == ISC_R_UNSET) {
|
2018-06-15 09:59:20 +02:00
|
|
|
*vresult = ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
if (*vresult == ISC_R_SUCCESS) {
|
|
|
|
*vresult = tvresult;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
if (prevname != NULL) {
|
2018-06-15 09:59:20 +02:00
|
|
|
result = verifyemptynodes(
|
|
|
|
vctx, name, prevname, isdelegation,
|
2018-06-15 09:59:20 +02:00
|
|
|
&vctx->nsec3paramset, &tvresult);
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
Decouple database and node lifetimes by adding node-specific vtables
All databases in the codebase follow the same structure: a database is
an associative container from DNS names to nodes, and each node is an
associative container from RR types to RR data.
Each database implementation (qpzone, qpcache, sdlz, builtin, dyndb) has
its own corresponding node type (qpznode, qpcnode, etc). However, some
code needs to work with nodes generically regardless of their specific
type - for example, to acquire locks, manage references, or
register/unregister slabs from the heap.
Currently, these generic node operations are implemented as methods in
the database vtable, which creates problematic coupling between database
and node lifetimes. If a node outlives its parent database, the node
destructor will destroy all RR data, and each RR data destructor will
try to unregister from heaps by calling a virtual function from the
database vtable. Since the database was already freed, this causes a
crash.
This commit breaks the coupling by standardizing the layout of all
database nodes, adding a dedicated vtable for node operations, and
moving node-specific methods from the database vtable to the node
vtable.
2025-06-05 11:51:29 +02:00
|
|
|
dns_db_detachnode(&node);
|
2018-06-15 09:59:20 +02:00
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
} else {
|
2018-06-15 09:59:20 +02:00
|
|
|
prevname = dns_fixedname_name(&fprevname);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2021-05-21 17:20:44 -07:00
|
|
|
dns_name_copy(name, prevname);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (*vresult == ISC_R_SUCCESS) {
|
|
|
|
*vresult = tvresult;
|
|
|
|
}
|
Decouple database and node lifetimes by adding node-specific vtables
All databases in the codebase follow the same structure: a database is
an associative container from DNS names to nodes, and each node is an
associative container from RR types to RR data.
Each database implementation (qpzone, qpcache, sdlz, builtin, dyndb) has
its own corresponding node type (qpznode, qpcnode, etc). However, some
code needs to work with nodes generically regardless of their specific
type - for example, to acquire locks, manage references, or
register/unregister slabs from the heap.
Currently, these generic node operations are implemented as methods in
the database vtable, which creates problematic coupling between database
and node lifetimes. If a node outlives its parent database, the node
destructor will destroy all RR data, and each RR data destructor will
try to unregister from heaps by calling a virtual function from the
database vtable. Since the database was already freed, this causes a
crash.
This commit breaks the coupling by standardizing the layout of all
database nodes, adding a dedicated vtable for node operations, and
moving node-specific methods from the database vtable to the node
vtable.
2025-06-05 11:51:29 +02:00
|
|
|
dns_db_detachnode(&node);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
dns_dbiterator_destroy(&dbiter);
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_db_createiterator(vctx->db, DNS_DB_NSEC3ONLY, &dbiter);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx, "dns_db_createiterator(): %s",
|
|
|
|
isc_result_totext(result));
|
|
|
|
return result;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2025-03-26 22:49:03 -07:00
|
|
|
DNS_DBITERATOR_FOREACH(dbiter) {
|
2018-06-15 09:59:20 +02:00
|
|
|
result = dns_dbiterator_current(dbiter, &node, name);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
|
|
|
|
zoneverify_log_error(vctx,
|
|
|
|
"dns_dbiterator_current(): %s",
|
|
|
|
isc_result_totext(result));
|
|
|
|
goto done;
|
|
|
|
}
|
2020-11-25 17:52:22 +11:00
|
|
|
result = verifynode(vctx, name, node, false, dstkeys, nkeys,
|
2018-06-15 09:59:20 +02:00
|
|
|
NULL, NULL, NULL, NULL);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
zoneverify_log_error(vctx, "verifynode: %s",
|
|
|
|
isc_result_totext(result));
|
Decouple database and node lifetimes by adding node-specific vtables
All databases in the codebase follow the same structure: a database is
an associative container from DNS names to nodes, and each node is an
associative container from RR types to RR data.
Each database implementation (qpzone, qpcache, sdlz, builtin, dyndb) has
its own corresponding node type (qpznode, qpcnode, etc). However, some
code needs to work with nodes generically regardless of their specific
type - for example, to acquire locks, manage references, or
register/unregister slabs from the heap.
Currently, these generic node operations are implemented as methods in
the database vtable, which creates problematic coupling between database
and node lifetimes. If a node outlives its parent database, the node
destructor will destroy all RR data, and each RR data destructor will
try to unregister from heaps by calling a virtual function from the
database vtable. Since the database was already freed, this causes a
crash.
This commit breaks the coupling by standardizing the layout of all
database nodes, adding a dedicated vtable for node operations, and
moving node-specific methods from the database vtable to the node
vtable.
2025-06-05 11:51:29 +02:00
|
|
|
dns_db_detachnode(&node);
|
2018-06-15 09:59:20 +02:00
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
result = record_found(vctx, name, node, &vctx->nsec3paramset);
|
Decouple database and node lifetimes by adding node-specific vtables
All databases in the codebase follow the same structure: a database is
an associative container from DNS names to nodes, and each node is an
associative container from RR types to RR data.
Each database implementation (qpzone, qpcache, sdlz, builtin, dyndb) has
its own corresponding node type (qpznode, qpcnode, etc). However, some
code needs to work with nodes generically regardless of their specific
type - for example, to acquire locks, manage references, or
register/unregister slabs from the heap.
Currently, these generic node operations are implemented as methods in
the database vtable, which creates problematic coupling between database
and node lifetimes. If a node outlives its parent database, the node
destructor will destroy all RR data, and each RR data destructor will
try to unregister from heaps by calling a virtual function from the
database vtable. Since the database was already freed, this causes a
crash.
This commit breaks the coupling by standardizing the layout of all
database nodes, adding a dedicated vtable for node operations, and
moving node-specific methods from the database vtable to the node
vtable.
2025-06-05 11:51:29 +02:00
|
|
|
dns_db_detachnode(&node);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
result = ISC_R_SUCCESS;
|
|
|
|
|
|
|
|
done:
|
2020-11-25 17:52:22 +11:00
|
|
|
while (nkeys-- > 0U) {
|
|
|
|
dst_key_free(&dstkeys[nkeys]);
|
|
|
|
}
|
2023-08-23 08:56:31 +02:00
|
|
|
isc_mem_cput(vctx->mctx, dstkeys, count, sizeof(*dstkeys));
|
2020-11-25 17:52:22 +11:00
|
|
|
if (dbiter != NULL) {
|
|
|
|
dns_dbiterator_destroy(&dbiter);
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
return result;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
static isc_result_t
|
2019-07-21 08:07:20 -04:00
|
|
|
check_bad_algorithms(const vctx_t *vctx, void (*report)(const char *, ...)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
char algbuf[DNS_SECALG_FORMATSIZE];
|
2018-04-17 08:29:14 -07:00
|
|
|
bool first = true;
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2020-06-18 17:26:46 -03:00
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(vctx->bad_algorithms); i++) {
|
2018-06-15 09:59:20 +02:00
|
|
|
if (vctx->bad_algorithms[i] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (first) {
|
2019-07-21 08:07:20 -04:00
|
|
|
report("The zone is not fully signed "
|
|
|
|
"for the following algorithms:");
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2025-04-01 00:12:52 +11:00
|
|
|
dst_algorithm_format(i, algbuf, sizeof(algbuf));
|
2019-07-21 08:07:20 -04:00
|
|
|
report(" %s", algbuf);
|
2018-04-17 08:29:14 -07:00
|
|
|
first = false;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
if (!first) {
|
2021-01-05 12:06:23 +01:00
|
|
|
report(".");
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
return first ? ISC_R_SUCCESS : ISC_R_FAILURE;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
static void
|
2019-07-30 08:43:30 -07:00
|
|
|
print_summary(const vctx_t *vctx, bool keyset_kskonly,
|
|
|
|
void (*report)(const char *, ...)) {
|
2018-06-15 09:59:20 +02:00
|
|
|
char algbuf[DNS_SECALG_FORMATSIZE];
|
|
|
|
|
2021-01-05 12:06:23 +01:00
|
|
|
report("Zone fully signed:");
|
2020-06-18 17:26:46 -03:00
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(vctx->ksk_algorithms); i++) {
|
2018-06-15 09:59:20 +02:00
|
|
|
if ((vctx->ksk_algorithms[i] == 0) &&
|
|
|
|
(vctx->standby_ksk[i] == 0) &&
|
|
|
|
(vctx->revoked_ksk[i] == 0) &&
|
|
|
|
(vctx->zsk_algorithms[i] == 0) &&
|
|
|
|
(vctx->standby_zsk[i] == 0) && (vctx->revoked_zsk[i] == 0))
|
|
|
|
{
|
|
|
|
continue;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2025-04-01 00:12:52 +11:00
|
|
|
dst_algorithm_format(i, algbuf, sizeof(algbuf));
|
2019-07-21 08:07:20 -04:00
|
|
|
report("Algorithm: %s: KSKs: "
|
2021-01-05 12:06:23 +01:00
|
|
|
"%u active, %u stand-by, %u revoked",
|
2019-07-21 08:07:20 -04:00
|
|
|
algbuf, vctx->ksk_algorithms[i], vctx->standby_ksk[i],
|
|
|
|
vctx->revoked_ksk[i]);
|
|
|
|
report("%*sZSKs: "
|
2021-01-05 12:06:23 +01:00
|
|
|
"%u active, %u %s, %u revoked",
|
2019-07-21 08:07:20 -04:00
|
|
|
(int)strlen(algbuf) + 13, "", vctx->zsk_algorithms[i],
|
|
|
|
vctx->standby_zsk[i],
|
|
|
|
keyset_kskonly ? "present" : "stand-by",
|
|
|
|
vctx->revoked_zsk[i]);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
isc_result_t
|
2018-06-15 09:59:20 +02:00
|
|
|
dns_zoneverify_dnssec(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
|
2018-06-28 13:38:39 +02:00
|
|
|
dns_name_t *origin, dns_keytable_t *secroots,
|
2018-04-17 08:29:14 -07:00
|
|
|
isc_mem_t *mctx, bool ignore_kskflag, bool keyset_kskonly,
|
2019-07-21 08:07:20 -04:00
|
|
|
void (*report)(const char *, ...)) {
|
2018-06-28 13:38:39 +02:00
|
|
|
const char *keydesc = (secroots == NULL ? "self-signed" : "trusted");
|
2018-06-15 09:59:20 +02:00
|
|
|
isc_result_t result, vresult = ISC_R_UNSET;
|
|
|
|
vctx_t vctx;
|
|
|
|
|
2021-10-19 11:46:37 +02:00
|
|
|
vctx_init(&vctx, mctx, zone, db, ver, origin, secroots);
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
result = check_apex_rrsets(&vctx);
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
result = check_dnskey(&vctx);
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
if (ignore_kskflag) {
|
|
|
|
if (!vctx.goodksk && !vctx.goodzsk) {
|
2018-06-28 13:38:39 +02:00
|
|
|
zoneverify_log_error(&vctx, "No %s DNSKEY found",
|
|
|
|
keydesc);
|
2018-06-15 09:59:20 +02:00
|
|
|
result = ISC_R_FAILURE;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
} else if (!vctx.goodksk) {
|
2018-06-28 13:38:39 +02:00
|
|
|
zoneverify_log_error(&vctx, "No %s KSK DNSKEY found", keydesc);
|
2018-06-15 09:59:20 +02:00
|
|
|
result = ISC_R_FAILURE;
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2019-07-21 08:07:20 -04:00
|
|
|
determine_active_algorithms(&vctx, ignore_kskflag, keyset_kskonly,
|
|
|
|
report);
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
result = verify_nodes(&vctx, &vresult);
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
result = verify_nsec3_chains(&vctx, mctx);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (vresult == ISC_R_UNSET) {
|
2018-06-15 09:59:20 +02:00
|
|
|
vresult = ISC_R_SUCCESS;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
|
|
|
if (result != ISC_R_SUCCESS && vresult == ISC_R_SUCCESS) {
|
2018-06-15 09:59:20 +02:00
|
|
|
vresult = result;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2019-07-21 08:07:20 -04:00
|
|
|
result = check_bad_algorithms(&vctx, report);
|
2018-06-15 09:59:20 +02:00
|
|
|
if (result != ISC_R_SUCCESS) {
|
2021-01-05 12:06:23 +01:00
|
|
|
report("DNSSEC completeness test failed.");
|
2018-06-15 09:59:20 +02:00
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
result = vresult;
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
2021-01-05 12:06:23 +01:00
|
|
|
report("DNSSEC completeness test failed (%s).",
|
2021-10-04 17:14:53 +02:00
|
|
|
isc_result_totext(result));
|
2018-06-15 09:59:20 +02:00
|
|
|
goto done;
|
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
if (vctx.goodksk || ignore_kskflag) {
|
2019-07-21 08:07:20 -04:00
|
|
|
print_summary(&vctx, keyset_kskonly, report);
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|
2018-06-15 09:59:20 +02:00
|
|
|
|
2018-06-15 09:59:20 +02:00
|
|
|
done:
|
2018-06-15 09:59:20 +02:00
|
|
|
vctx_destroy(&vctx);
|
2018-06-15 09:59:20 +02:00
|
|
|
|
|
|
|
return result;
|
2018-06-15 09:59:20 +02:00
|
|
|
}
|