2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-31 14:35:26 +00:00

chg: usr: When forwarding, query with CD=0 first

Previously, when queries were forwarded to a remote resolver, the CD (checking disabled) bit was used, which could lead to bogus data being retrieved that might have been corrected if validation had been permitted. The CD bit is now only used as a fallback if an initial query without CD fails. See #5132.

Merge branch '5132-cd-retry' into 'main'

See merge request isc-projects/bind9!10024
This commit is contained in:
Evan Hunt
2025-03-25 01:11:07 +00:00
10 changed files with 156 additions and 17 deletions

View File

@@ -67,6 +67,10 @@ ns.bogus A 10.53.0.3
badds NS ns.badds
ns.badds A 10.53.0.3
; A subdomain with a corrupt DS, but locally trusted by the forwarder
localkey NS ns.localkey
ns.localkey A 10.53.0.3
; A dynamic secure subdomain
dynamic NS dynamic
dynamic A 10.53.0.3

View File

@@ -59,7 +59,7 @@ zonefile=example.db
# Get the DS records for the "example." zone.
for subdomain in digest-alg-unsupported ds-unsupported secure badds \
bogus dynamic keyless nsec3 optout \
bogus localkey dynamic keyless nsec3 optout \
nsec3-unknown optout-unknown multiple rsasha256 rsasha512 \
kskonly update-nsec3 auto-nsec auto-nsec3 secure.below-cname \
ttlpatch split-dnssec split-smart expired expiring upper lower \

View File

@@ -103,6 +103,12 @@ zone "badds.example" {
allow-update { any; };
};
zone "localkey.example" {
type primary;
file "localkey.example.db.signed";
allow-update { any; };
};
zone "dynamic.example" {
type primary;
file "dynamic.example.db.signed";

View File

@@ -620,6 +620,21 @@ cat "$infile" "$keyname.key" >"$zonefile"
"$SIGNER" -P -o "$zone" "$zonefile" >/dev/null
sed -e 's/bogus/badds/g' <dsset-bogus.example. >dsset-badds.example.
#
# Same as badds, but locally trusted by the forwarder
#
zone=localkey.example.
infile=bogus.example.db.in
zonefile=localkey.example.db
keyname=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone "$zone")
cat "$infile" "$keyname.key" >"$zonefile"
"$SIGNER" -P -o "$zone" "$zonefile" >/dev/null
sed -e 's/bogus/localkey/g' <dsset-bogus.example. >dsset-localkey.example.
keyfile_to_static_keys $keyname >../ns9/trusted-localkey.conf
#
# A zone with future signatures.
#

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
// NS4
options {
query-source address 10.53.0.4;
notify-source 10.53.0.4;
transfer-source 10.53.0.4;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.4; };
listen-on-v6 { none; };
recursion yes;
dnssec-validation yes;
minimal-responses no;
};
# Note: This is deliberately wrong! The bind.keys file contains
# the real DNS root key, so it won't work with the local toy
# root zones used in the tests. This is to test a forwarder
# talking to a resolver with a misconfigured trust anchor.
include "../../../../../bind.keys";
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
zone "." {
type hint;
file "../../_common/root.hint";
};
zone "corp" {
type static-stub;
server-addresses { 10.53.0.2; };
};

View File

@@ -38,3 +38,4 @@ controls {
};
include "trusted.conf";
include "trusted-localkey.conf";

View File

@@ -4723,5 +4723,56 @@ n=$((n + 1))
if [ "$ret" -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
echo_i "checking validating forwarder behavior with mismatching NS ($n)"
ret=0
rndccmd 10.53.0.4 flush 2>&1 | sed 's/^/ns4 /' | cat_i
$DIG +tcp +cd -p "$PORT" -t ns inconsistent @10.53.0.9 >dig.out.ns9.test$n.1 || ret=1
grep "ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1" dig.out.ns9.test$n.1 >/dev/null || ret=1
grep "flags:.*ad.*QUERY" dig.out.ns9.test$n.1 >/dev/null && ret=1
$DIG +tcp +cd +dnssec -p "$PORT" -t ns inconsistent @10.53.0.9 >dig.out.ns9.test$n.2 || ret=1
grep "ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1" dig.out.ns9.test$n.2 >/dev/null || ret=1
grep "flags:.*ad.*QUERY" dig.out.ns9.test$n.2 >/dev/null && ret=1
$DIG +tcp +dnssec -p "$PORT" -t ns inconsistent @10.53.0.9 >dig.out.ns9.test$n.3 || ret=1
grep "ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1" dig.out.ns9.test$n.3 >/dev/null || ret=1
grep "flags:.*ad.*QUERY" dig.out.ns9.test$n.3 >/dev/null || ret=1
n=$((n + 1))
if [ "$ret" -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
echo_i "checking forwarder CD behavior (DS mismatch and local trust anchor) ($n)"
ret=0
rndccmd 10.53.0.4 flush 2>&1 | sed 's/^/ns4 /' | cat_i
# confirm invalid DS produces SERVFAIL in resolver
$DIG +tcp +dnssec -p "$PORT" @10.53.0.4 localkey.example soa >dig.out.ns4.test$n || ret=1
grep "status: SERVFAIL" dig.out.ns4.test$n >/dev/null || ret=1
# check that lookup using forwarder succeeds and that SERVFAIL was received
nextpart ns9/named.run >/dev/null
$DIG +tcp +dnssec -p "$PORT" @10.53.0.9 localkey.example soa >dig.out.ns9.test$n || ret=1
grep "status: NOERROR" dig.out.ns9.test$n >/dev/null || ret=1
grep "flags:.*ad.*QUERY" dig.out.ns9.test$n >/dev/null || ret=1
nextpart ns9/named.run | grep 'status: SERVFAIL' >/dev/null || ret=1
n=$((n + 1))
if [ "$ret" -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
copy_setports ns4/named5.conf.in ns4/named.conf
rndccmd 10.53.0.4 reconfig 2>&1 | sed 's/^/ns4 /' | cat_i
sleep 3
echo_i "checking forwarder CD behavior (forward server with bad trust anchor) ($n)"
ret=0
# confirm invalid trust anchor produces SERVFAIL in resolver
$DIG +tcp +dnssec -p "$PORT" @10.53.0.4 a.secure.example >dig.out.ns4.test$n || ret=1
grep "status: SERVFAIL" dig.out.ns4.test$n >/dev/null || ret=1
# check that lookup using forwarder succeeds and that SERVFAIL was received
nextpart ns9/named.run >/dev/null
$DIG +tcp +dnssec -p "$PORT" @10.53.0.9 a.secure.example soa >dig.out.ns9.test$n || ret=1
grep "status: NOERROR" dig.out.ns9.test$n >/dev/null || ret=1
grep "flags:.*ad.*QUERY" dig.out.ns9.test$n >/dev/null || ret=1
nextpart ns9/named.run | grep 'status: SERVFAIL' >/dev/null || ret=1
n=$((n + 1))
if [ "$ret" -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
echo_i "exit status: $status"
[ $status -eq 0 ] || exit 1

View File

@@ -104,6 +104,7 @@ pytestmark = pytest.mark.extra_artifacts(
"ns3/future.example.db",
"ns3/keyless.example.db",
"ns3/kskonly.example.db",
"ns3/localkey.example.db",
"ns3/lower.example.db",
"ns3/managed-future.example.db",
"ns3/multiple.example.db",
@@ -152,10 +153,10 @@ pytestmark = pytest.mark.extra_artifacts(
"ns4/named_dump.db",
"ns4/named_dump.db.*",
"ns5/revoked.conf",
"ns5/trusted.conf",
"ns6/optout-tld.db",
"ns7/split-rrsig.db",
"ns7/split-rrsig.db.unsplit",
"ns9/trusted-localkey.conf",
"signer/example.db",
"signer/example.db.after",
"signer/example.db.before",

View File

@@ -131,6 +131,11 @@ enum {
* possible. */
DNS_FETCHOPT_QMINFETCH = 1 << 16, /*%< Qmin fetch */
DNS_FETCHOPT_WANTZONEVERSION = 1 << 17, /*%< Request ZONEVERSION */
DNS_FETCHOPT_TRYCD = 1 << 18, /*%< Send the first query
* to a forwader with
* CD=0, but retry with CD=1
* if it returns SERVFAIL.
*/
/*% EDNS version bits: */
DNS_FETCHOPT_EDNSVERSIONSET = 1 << 23,

View File

@@ -2345,7 +2345,6 @@ resquery_send(resquery_t *query) {
dns_peer_t *peer = NULL;
dns_compress_t cctx;
bool useedns;
bool secure_domain;
bool tcp = ((query->options & DNS_FETCHOPT_TCP) != 0);
dns_ednsopt_t ednsopts[DNS_EDNSOPTIONS];
unsigned int ednsopt = 0;
@@ -2396,24 +2395,14 @@ resquery_send(resquery_t *query) {
*/
if ((query->options & DNS_FETCHOPT_NOCDFLAG) != 0) {
/* Do nothing */
} else if ((query->options & DNS_FETCHOPT_NOVALIDATE) != 0) {
} else if ((query->options & DNS_FETCHOPT_NOVALIDATE) != 0 ||
(query->options & DNS_FETCHOPT_TRYCD) != 0)
{
fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD;
} else if (res->view->enablevalidation &&
((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0))
{
bool checknta = ((query->options & DNS_FETCHOPT_NONTA) == 0);
bool ntacovered = false;
result = issecuredomain(res->view, fctx->name, fctx->type,
isc_time_seconds(&query->start),
checknta, &ntacovered, &secure_domain);
if (result != ISC_R_SUCCESS) {
secure_domain = false;
}
if (secure_domain ||
(ISFORWARDER(query->addrinfo) && ntacovered))
{
fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD;
}
query->options |= DNS_FETCHOPT_TRYCD;
}
/*
@@ -7788,6 +7777,8 @@ resquery_response_continue(void *arg, isc_result_t result) {
fetchctx_t *fctx = rctx->fctx;
resquery_t *query = rctx->query;
QTRACE("response_continue");
if (result != ISC_R_SUCCESS) {
FCTXTRACE3("signature check failed", result);
if (result == DNS_R_UNEXPECTEDTSIG ||
@@ -10006,6 +9997,8 @@ rctx_badserver(respctx_t *rctx, isc_result_t result) {
char code[64];
dns_rcode_t rcode = rctx->query->rmessage->rcode;
QTRACE("rctx_badserver");
if (rcode == dns_rcode_noerror || rcode == dns_rcode_yxdomain ||
rcode == dns_rcode_nxdomain)
{
@@ -10100,6 +10093,16 @@ rctx_badserver(respctx_t *rctx, isc_result_t result) {
}
query->addrinfo->flags |= FCTX_ADDRINFO_BADCOOKIE;
rctx->resend = true;
} else if (ISFORWARDER(query->addrinfo) &&
query->rmessage->rcode == dns_rcode_servfail &&
(query->options & DNS_FETCHOPT_TRYCD) != 0)
{
/*
* We got a SERVFAIL from a forwarder with
* CD=0; try again with CD=1.
*/
rctx->retryopts |= DNS_FETCHOPT_TRYCD;
rctx->resend = true;
} else {
rctx->broken_server = DNS_R_UNEXPECTEDRCODE;
rctx->next_server = true;