Hello, El 04/11/25 a las 08:57, Frank Richter escribió: > Hello, > > are you working on a patched version of bind9 1:9.16.50-1~deb11u3 to fix > CVE-2025-40778?
Debian 11 "bullseye" is currently maintained by the Debian LTS Team (https://wiki.debian.org/LTS). I can say that yes, a fix is being prepared for 9.16, shipped in bullseye. > > We’ve build bind9 with the attached patches from > https://gitlab.isc.org/isc-projects/bind9/-/commits/bind-9.16?ref_type=heads > We use this version since yesterday without problems so far. > > Kind regards, > Frank > > -- > Frank Richter, Chemnitz University of Technology, Germany > >From d9b5ef342916462bfd63391831d96afc80c12df3 Mon Sep 17 00:00:00 2001 > From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <[email protected]> > Date: Tue, 19 Aug 2025 19:22:18 +0200 > Subject: [PATCH] Use cryptographically-secure pseudo-random generator > everywhere > > It was discovered in an upcoming academic paper that a xoshiro128** > internal state can be recovered by an external 3rd party allowing to > predict UDP ports and DNS IDs in the outgoing queries. This could lead > to an attacker spoofing the DNS answers with great efficiency and > poisoning the DNS cache. > > Change the internal random generator to system CSPRNG with buffering to > avoid excessive syscalls. > > Thanks Omer Ben Simhon and Amit Klein of Hebrew University of Jerusalem > for responsibly reporting this to us. Very cool research! > > (cherry picked from commit cffcab9d5f3e709002f331b72498fcc229786ae2) > --- > lib/isc/include/isc/os.h | 5 + > lib/isc/include/isc/random.h | 2 +- > lib/isc/random.c | 181 ++++++++++++++++++----------------- > lib/isc/tests/random_test.c | 4 +- > 4 files changed, 102 insertions(+), 90 deletions(-) > > diff --git a/lib/isc/include/isc/os.h b/lib/isc/include/isc/os.h > index 585abc0b124..699cbb971ac 100644 > --- a/lib/isc/include/isc/os.h > +++ b/lib/isc/include/isc/os.h > @@ -20,6 +20,11 @@ > > ISC_LANG_BEGINDECLS > > +/*%< > + * Hardcode the L1 cacheline size of the CPU to 64. > + */ > +#define ISC_OS_CACHELINE_SIZE 64 > + > unsigned int > isc_os_ncpus(void); > /*%< > diff --git a/lib/isc/include/isc/random.h b/lib/isc/include/isc/random.h > index 1e30d0c87d5..fd55343778a 100644 > --- a/lib/isc/include/isc/random.h > +++ b/lib/isc/include/isc/random.h > @@ -20,7 +20,7 @@ > #include <isc/types.h> > > /*! \file isc/random.h > - * \brief Implements wrapper around a non-cryptographically secure > + * \brief Implements wrapper around a cryptographically secure > * pseudo-random number generator. > * > */ > diff --git a/lib/isc/random.c b/lib/isc/random.c > index b11c39f419d..3fc0ff7234b 100644 > --- a/lib/isc/random.c > +++ b/lib/isc/random.c > @@ -31,131 +31,136 @@ > */ > > #include <inttypes.h> > -#include <stdlib.h> > -#include <string.h> > -#include <unistd.h> > +#include <stdio.h> > > -#include <isc/once.h> > +#include <isc/os.h> > #include <isc/platform.h> > #include <isc/random.h> > -#include <isc/result.h> > #include <isc/thread.h> > -#include <isc/types.h> > #include <isc/util.h> > > #include "entropy_private.h" > > -/* > - * The specific implementation for PRNG is included as a C file > - * that has to provide a static variable named seed, and a function > - * uint32_t next(void) that provides next random number. > - * > - * The implementation must be thread-safe. > - */ > - > -/* > - * Two contestants have been considered: the xoroshiro family of the > - * functions by Villa&Blackman, and PCG by O'Neill. After > - * consideration, the xoshiro128starstar function has been chosen as > - * the uint32_t random number provider because it is very fast and has > - * good enough properties for our usage pattern. > - */ > -#include "xoshiro128starstar.c" > +#define ISC_RANDOM_BUFSIZE (ISC_OS_CACHELINE_SIZE / sizeof(uint32_t)) > > -ISC_THREAD_LOCAL isc_once_t isc_random_once = ISC_ONCE_INIT; > +ISC_THREAD_LOCAL uint32_t isc__random_pool[ISC_RANDOM_BUFSIZE]; > +ISC_THREAD_LOCAL size_t isc__random_pos = ISC_RANDOM_BUFSIZE; > > -static void > -isc_random_initialize(void) { > - int useed[4] = { 0, 0, 0, 1 }; > +static uint32_t > +random_u32(void) { > #if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION > /* > - * Set a constant seed to help in problem reproduction should fuzzing > - * find a crash or a hang. The seed array must be non-zero else > - * xoshiro128starstar will generate an infinite series of zeroes. > + * A fixed stream of numbers helps with problem reproduction when > + * fuzzing. The first result needs to be non-zero as expected by > + * random_test.c (it starts with ISC_RANDOM_BUFSIZE, see above). > */ > -#else /* if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */ > - isc_entropy_get(useed, sizeof(useed)); > + return (uint32_t)(isc__random_pos++); > #endif /* if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */ > - memmove(seed, useed, sizeof(seed)); > + > + if (isc__random_pos == ISC_RANDOM_BUFSIZE) { > + isc_entropy_get(isc__random_pool, sizeof(isc__random_pool)); > + isc__random_pos = 0; > + } > + > + return isc__random_pool[isc__random_pos++]; > } > > uint8_t > isc_random8(void) { > - RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) == > - ISC_R_SUCCESS); > - return (next() & 0xff); > + return (uint8_t)random_u32(); > } > > uint16_t > isc_random16(void) { > - RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) == > - ISC_R_SUCCESS); > - return (next() & 0xffff); > + return (uint16_t)random_u32(); > } > > uint32_t > isc_random32(void) { > - RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) == > - ISC_R_SUCCESS); > - return (next()); > + return random_u32(); > } > > void > isc_random_buf(void *buf, size_t buflen) { > - int i; > - uint32_t r; > + REQUIRE(buflen == 0 || buf != NULL); > > - REQUIRE(buf != NULL); > - REQUIRE(buflen > 0); > - > - RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) == > - ISC_R_SUCCESS); > - > - for (i = 0; i + sizeof(r) <= buflen; i += sizeof(r)) { > - r = next(); > - memmove((uint8_t *)buf + i, &r, sizeof(r)); > + if (buf == NULL || buflen == 0) { > + return; > } > - r = next(); > - memmove((uint8_t *)buf + i, &r, buflen % sizeof(r)); > - return; > + > + isc_entropy_get(buf, buflen); > } > > uint32_t > -isc_random_uniform(uint32_t upper_bound) { > - /* Copy of arc4random_uniform from OpenBSD */ > - uint32_t r, min; > - > - RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) == > - ISC_R_SUCCESS); > - > - if (upper_bound < 2) { > - return (0); > - } > - > -#if (ULONG_MAX > 0xffffffffUL) > - min = 0x100000000UL % upper_bound; > -#else /* if (ULONG_MAX > 0xffffffffUL) */ > - /* Calculate (2**32 % upper_bound) avoiding 64-bit math */ > - if (upper_bound > 0x80000000) { > - min = 1 + ~upper_bound; /* 2**32 - upper_bound */ > - } else { > - /* (2**32 - (x * 2)) % x == 2**32 % x when x <= 2**31 */ > - min = ((0xffffffff - (upper_bound * 2)) + 1) % upper_bound; > - } > -#endif /* if (ULONG_MAX > 0xffffffffUL) */ > - > +isc_random_uniform(uint32_t limit) { > /* > - * This could theoretically loop forever but each retry has > - * p > 0.5 (worst case, usually far better) of selecting a > - * number inside the range we need, so it should rarely need > - * to re-roll. > + * Daniel Lemire's nearly-divisionless unbiased bounded random numbers. > + * > + * https://lemire.me/blog/?p=17551 > + * > + * The raw random number generator `next()` returns a 32-bit value. > + * We do a 64-bit multiply `next() * limit` and treat the product as a > + * 32.32 fixed-point value less than the limit. Our result will be the > + * integer part (upper 32 bits), and we will use the fraction part > + * (lower 32 bits) to determine whether or not we need to resample. > */ > - for (;;) { > - r = next(); > - if (r >= min) { > - break; > + uint64_t num = (uint64_t)random_u32() * (uint64_t)limit; > + /* > + * In the fast path, we avoid doing a division in most cases by > + * comparing the fraction part of `num` with the limit, which is > + * a slight over-estimate for the exact resample threshold. > + */ > + if ((uint32_t)(num) < limit) { > + /* > + * We are in the slow path where we re-do the approximate test > + * more accurately. The exact threshold for the resample loop > + * is the remainder after dividing the raw RNG limit `1 << 32` > + * by the caller's limit. We use a trick to calculate it > + * within 32 bits: > + * > + * (1 << 32) % limit > + * == ((1 << 32) - limit) % limit > + * == (uint32_t)(-limit) % limit > + * > + * This division is safe: we know that `limit` is strictly > + * greater than zero because of the slow-path test above. > + */ > + uint32_t residue = (uint32_t)(-limit) % limit; > + /* > + * Unless we get one of `N = (1 << 32) - residue` valid > + * values, we reject the sample. This `N` is a multiple of > + * `limit`, so our results will be unbiased; and `N` is the > + * largest multiple that fits in 32 bits, so rejections are as > + * rare as possible. > + * > + * There are `limit` possible values for the integer part of > + * our fixed-point number. Each one corresponds to `N/limit` > + * or `N/limit + 1` possible fraction parts. For our result to > + * be unbiased, every possible integer part must have the same > + * number of possible valid fraction parts. So, when we get > + * the superfluous value in the `N/limit + 1` cases, we need > + * to reject and resample. > + * > + * Because of the multiplication, the possible values in the > + * fraction part are equally spaced by `limit`, with varying > + * gaps at each end of the fraction's 32-bit range. We will > + * choose a range of size `N` (a multiple of `limit`) into > + * which valid fraction values must fall, with the rest of the > + * 32-bit range covered by the `residue`. Lemire's paper says > + * that exactly `N/limit` possible values spaced apart by > + * `limit` will fit into our size `N` valid range, regardless > + * of the size of the end gaps, the phase alignment of the > + * values, or the position of the range. > + * > + * So, when a fraction value falls in the `residue` outside > + * our valid range, it is superfluous, and we resample. > + */ > + while ((uint32_t)(num) < residue) { > + num = (uint64_t)random_u32() * (uint64_t)limit; > } > } > - > - return (r % upper_bound); > + /* > + * Return the integer part (upper 32 bits). > + */ > + return (uint32_t)(num >> 32); > } > diff --git a/lib/isc/tests/random_test.c b/lib/isc/tests/random_test.c > index 8025583f87e..57b6d997b01 100644 > --- a/lib/isc/tests/random_test.c > +++ b/lib/isc/tests/random_test.c > @@ -347,7 +347,9 @@ random_test(pvalue_func_t *func, isc_random_func > test_func) { > } > break; > case ISC_RANDOM_BYTES: > - isc_random_buf(values, sizeof(values)); > + for (i = 0; i < ARRAY_SIZE(values); i++) { > + values[i] = isc_random32(); > + } > break; > case ISC_RANDOM_UNIFORM: > uniform_values = (uint16_t *)values; > -- > GitLab > > >From 2f0f44d493c382a7f0a3adfe7c4976b18a3d480b Mon Sep 17 00:00:00 2001 > From: Evan Hunt <[email protected]> > Date: Mon, 29 Sep 2025 21:46:59 -0700 > Subject: [PATCH 1/3] Tighten restrictions on caching NS RRsets in authority > section > > To prevent certain spoofing attacks, a new check has been added > to the existing rules for whether NS data can be cached: the owner > name of the NS RRset must be an ancestor of the name being queried. > > (cherry picked from commit fa153f791f9324bf84abf8d259e11c0531fe6e25) > --- > lib/dns/resolver.c | 4 +++- > 1 file changed, 3 insertions(+), 1 deletion(-) > > diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c > index 737f83cdbf5..7a2768c1416 100644 > --- a/lib/dns/resolver.c > +++ b/lib/dns/resolver.c > @@ -9243,7 +9243,9 @@ rctx_authority_positive(respctx_t *rctx) { > dns_message_currentname(rctx->query->rmessage, > DNS_SECTION_AUTHORITY, &name); > > - if (!name_external(name, dns_rdatatype_ns, fctx)) { > + if (!name_external(name, dns_rdatatype_ns, fctx) && > + dns_name_issubdomain(&fctx->name, name)) > + { > dns_rdataset_t *rdataset = NULL; > > /* > -- > GitLab > > > >From 50479358efdf432d690415131b74b5df158a9d69 Mon Sep 17 00:00:00 2001 > From: Evan Hunt <[email protected]> > Date: Mon, 29 Sep 2025 21:52:43 -0700 > Subject: [PATCH 2/3] Further restrict addresses that are cached when > processing referrals > > Use the owner name of the NS record as the bailwick apex name > when determining which additional records to cache, rather than > the name of the delegating zone (or a parent thereof). > > (cherry picked from commit a41054e9e606a61f1b3c8bc0c54e2f1059347165) > --- > lib/dns/resolver.c | 13 +++++++------ > 1 file changed, 7 insertions(+), 6 deletions(-) > > diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c > index 7a2768c1416..168fcc5e808 100644 > --- a/lib/dns/resolver.c > +++ b/lib/dns/resolver.c > @@ -7162,8 +7162,9 @@ mark_related(dns_name_t *name, dns_rdataset_t > *rdataset, bool external, > * subdomain or because it's below a forward declaration or a > * locally served zone. > */ > -static bool > -name_external(const dns_name_t *name, dns_rdatatype_t type, fetchctx_t > *fctx) { > +static inline bool > +name_external(const dns_name_t *name, dns_rdatatype_t type, respctx_t *rctx) > { > + fetchctx_t *fctx = rctx->fctx; > isc_result_t result; > dns_forwarders_t *forwarders = NULL; > dns_fixedname_t fixed, zfixed; > @@ -7176,7 +7177,7 @@ name_external(const dns_name_t *name, dns_rdatatype_t > type, fetchctx_t *fctx) { > dns_namereln_t rel; > > apex = (ISDUALSTACK(fctx->addrinfo) || !ISFORWARDER(fctx->addrinfo)) > - ? &fctx->domain > + ? rctx->ns_name != NULL ? rctx->ns_name : &fctx->domain > : fctx->fwdname; > > /* > @@ -7285,7 +7286,7 @@ check_section(void *arg, const dns_name_t *addname, > dns_rdatatype_t type, > result = dns_message_findname(rctx->query->rmessage, section, addname, > dns_rdatatype_any, 0, &name, NULL); > if (result == ISC_R_SUCCESS) { > - external = name_external(name, type, fctx); > + external = name_external(name, type, rctx); > if (type == dns_rdatatype_a) { > for (rdataset = ISC_LIST_HEAD(name->list); > rdataset != NULL; > @@ -8920,7 +8921,7 @@ rctx_answer_scan(respctx_t *rctx) { > /* > * Don't accept DNAME from parent namespace. > */ > - if (name_external(name, dns_rdatatype_dname, fctx)) { > + if (name_external(name, dns_rdatatype_dname, rctx)) { > continue; > } > > @@ -9243,7 +9244,7 @@ rctx_authority_positive(respctx_t *rctx) { > dns_message_currentname(rctx->query->rmessage, > DNS_SECTION_AUTHORITY, &name); > > - if (!name_external(name, dns_rdatatype_ns, fctx) && > + if (!name_external(name, dns_rdatatype_ns, rctx) && > dns_name_issubdomain(&fctx->name, name)) > { > dns_rdataset_t *rdataset = NULL; > -- > GitLab > > > >From 33a7db1fe964e55b76b4ac003ecc56cc67028bd9 Mon Sep 17 00:00:00 2001 > From: Evan Hunt <[email protected]> > Date: Mon, 29 Sep 2025 21:57:48 -0700 > Subject: [PATCH 3/3] Retry lookups with unsigned DNAME over TCP > > To prevent spoofed unsigned DNAME responses being accepted retry > response with unsigned DNAMEs over TCP if the response is not TSIG > signed or there isn't a good DNS CLIENT COOKIE. > > To prevent test failures, this required adding TCP support to the > ans3 and ans4 servers in the chain system test. > > (cherry picked from commit 2e40705c06831988106335ed77db3cf924d431f6) > --- > bin/tests/system/chain/ans3/ans.pl | 143 ------------------- > bin/tests/system/chain/ans3/ans.py | 217 +++++++++++++++++++++++++++++ > bin/tests/system/chain/ans4/ans.py | 57 ++++++-- > lib/dns/include/dns/message.h | 8 ++ > lib/dns/message.c | 12 ++ > lib/dns/resolver.c | 99 ++++++++++--- > 6 files changed, 368 insertions(+), 168 deletions(-) > delete mode 100644 bin/tests/system/chain/ans3/ans.pl > create mode 100644 bin/tests/system/chain/ans3/ans.py > > diff --git a/bin/tests/system/chain/ans3/ans.pl > b/bin/tests/system/chain/ans3/ans.pl > deleted file mode 100644 > index e42240be63a..00000000000 > --- a/bin/tests/system/chain/ans3/ans.pl > +++ /dev/null > @@ -1,143 +0,0 @@ > -#!/usr/bin/env perl > - > -# 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. > - > -use strict; > -use warnings; > - > -use IO::File; > -use Getopt::Long; > -use Net::DNS::Nameserver; > - > -my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!"; > -print $pidf "$$\n" or die "cannot write pid file: $!"; > -$pidf->close or die "cannot close pid file: $!"; > -sub rmpid { unlink "ans.pid"; exit 1; }; > -sub term { }; > - > -$SIG{INT} = \&rmpid; > -if ($Net::DNS::VERSION > 1.41) { > - $SIG{TERM} = \&term; > -} else { > - $SIG{TERM} = \&rmpid; > -} > - > -my $localaddr = "10.53.0.3"; > - > -my $localport = int($ENV{'PORT'}); > -if (!$localport) { $localport = 5300; } > - > -my $verbose = 0; > -my $ttl = 60; > -my $zone = "example.broken"; > -my $nsname = "ns3.$zone"; > -my $synth = "synth-then-dname.$zone"; > -my $synth2 = "synth2-then-dname.$zone"; > - > -sub reply_handler { > - my ($qname, $qclass, $qtype, $peerhost, $query, $conn) = @_; > - my ($rcode, @ans, @auth, @add); > - > - print ("request: $qname/$qtype\n"); > - STDOUT->flush(); > - > - if ($qname eq "example.broken") { > - if ($qtype eq "SOA") { > - my $rr = new Net::DNS::RR("$qname $ttl $qclass SOA . . 0 0 0 0 0"); > - push @ans, $rr; > - } elsif ($qtype eq "NS") { > - my $rr = new Net::DNS::RR("$qname $ttl $qclass NS $nsname"); > - push @ans, $rr; > - $rr = new Net::DNS::RR("$nsname $ttl $qclass A $localaddr"); > - push @add, $rr; > - } > - $rcode = "NOERROR"; > - } elsif ($qname eq "cname-to-$synth2") { > - my $rr = new Net::DNS::RR("$qname $ttl $qclass CNAME name.$synth2"); > - push @ans, $rr; > - $rr = new Net::DNS::RR("name.$synth2 $ttl $qclass CNAME name"); > - push @ans, $rr; > - $rr = new Net::DNS::RR("$synth2 $ttl $qclass DNAME ."); > - push @ans, $rr; > - $rcode = "NOERROR"; > - } elsif ($qname eq "$synth" || $qname eq "$synth2") { > - if ($qtype eq "DNAME") { > - my $rr = new Net::DNS::RR("$qname $ttl $qclass DNAME ."); > - push @ans, $rr; > - } > - $rcode = "NOERROR"; > - } elsif ($qname eq "name.$synth") { > - my $rr = new Net::DNS::RR("$qname $ttl $qclass CNAME name."); > - push @ans, $rr; > - $rr = new Net::DNS::RR("$synth $ttl $qclass DNAME ."); > - push @ans, $rr; > - $rcode = "NOERROR"; > - } elsif ($qname eq "name.$synth2") { > - my $rr = new Net::DNS::RR("$qname $ttl $qclass CNAME name."); > - push @ans, $rr; > - $rr = new Net::DNS::RR("$synth2 $ttl $qclass DNAME ."); > - push @ans, $rr; > - $rcode = "NOERROR"; > - # The following three code branches referring to the "example.dname" > - # zone are necessary for the resolver variant of the CVE-2021-25215 > - # regression test to work. A named instance cannot be used for > - # serving the DNAME records below as a version of BIND vulnerable to > - # CVE-2021-25215 would crash while answering the queries asked by > - # the tested resolver. > - } elsif ($qname eq "ns3.example.dname") { > - if ($qtype eq "A") { > - my $rr = new Net::DNS::RR("$qname $ttl $qclass A 10.53.0.3"); > - push @ans, $rr; > - } > - if ($qtype eq "AAAA") { > - my $rr = new Net::DNS::RR("example.dname. $ttl $qclass SOA . . > 0 0 0 0 $ttl"); > - push @auth, $rr; > - } > - $rcode = "NOERROR"; > - } elsif ($qname eq "self.example.self.example.dname") { > - my $rr = new Net::DNS::RR("self.example.dname. $ttl $qclass DNAME > dname."); > - push @ans, $rr; > - $rr = new Net::DNS::RR("$qname $ttl $qclass CNAME self.example.dname."); > - push @ans, $rr; > - $rcode = "NOERROR"; > - } elsif ($qname eq "self.example.dname") { > - if ($qtype eq "DNAME") { > - my $rr = new Net::DNS::RR("$qname $ttl $qclass DNAME dname."); > - push @ans, $rr; > - } > - $rcode = "NOERROR"; > - } else { > - $rcode = "REFUSED"; > - } > - return ($rcode, \@ans, \@auth, \@add, { aa => 1 }); > -} > - > -GetOptions( > - 'port=i' => \$localport, > - 'verbose!' => \$verbose, > -); > - > -my $ns = Net::DNS::Nameserver->new( > - LocalAddr => $localaddr, > - LocalPort => $localport, > - ReplyHandler => \&reply_handler, > - Verbose => $verbose, > -); > - > -if ($Net::DNS::VERSION >= 1.42) { > - $ns->start_server(); > - select(undef, undef, undef, undef); > - $ns->stop_server(); > - unlink "ans.pid"; > -} else { > - $ns->main_loop; > -} > diff --git a/bin/tests/system/chain/ans3/ans.py > b/bin/tests/system/chain/ans3/ans.py > new file mode 100644 > index 00000000000..0a031c1145b > --- /dev/null > +++ b/bin/tests/system/chain/ans3/ans.py > @@ -0,0 +1,217 @@ > +# 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. > + > +############################################################################ > +# ans.py: See README.anspy for details. > +############################################################################ > + > +from __future__ import print_function > +import os > +import sys > +import signal > +import socket > +import select > +from datetime import datetime, timedelta > +import functools > + > +import dns, dns.message, dns.query > +from dns.rdatatype import * > +from dns.rdataclass import * > +from dns.rcode import * > +from dns.name import * > + > + > +############################################################################ > +# Respond to a DNS query. > +############################################################################ > +def create_response(msg): > + ttl = 60 > + zone = "example.broken." > + nsname = f"ns3.{zone}" > + synth = f"synth-then-dname.{zone}" > + synth2 = f"synth2-then-dname.{zone}" > + > + m = dns.message.from_wire(msg) > + qname = m.question[0].name.to_text() > + > + # prepare the response and convert to wire format > + r = dns.message.make_response(m) > + > + # get qtype > + rrtype = m.question[0].rdtype > + qtype = dns.rdatatype.to_text(rrtype) > + print(f"request: {qname}/{qtype}") > + > + rcode = "NOERROR" > + if qname == zone: > + if qtype == "SOA": > + r.answer.append(dns.rrset.from_text(qname, ttl, IN, SOA, ". . 0 > 0 0 0 0")) > + elif qtype == "NS": > + r.answer.append(dns.rrset.from_text(qname, ttl, IN, NS, nsname)) > + r.additional.append(dns.rrset.from_text(nsname, ttl, IN, A, ip4)) > + elif qname == f"cname-to-{synth2}": > + r.answer.append(dns.rrset.from_text(qname, ttl, IN, CNAME, > f"name.{synth2}")) > + r.answer.append(dns.rrset.from_text(f"name.{synth2}", ttl, IN, > CNAME, "name.")) > + r.answer.append(dns.rrset.from_text(synth2, ttl, IN, DNAME, ".")) > + elif qname == f"{synth}" or qname == f"{synth2}": > + if qtype == "DNAME": > + r.answer.append(dns.rrset.from_text(qname, ttl, IN, DNAME, ".")) > + elif qname == f"name.{synth}": > + r.answer.append(dns.rrset.from_text(qname, ttl, IN, CNAME, "name.")) > + r.answer.append(dns.rrset.from_text(synth, ttl, IN, DNAME, ".")) > + elif qname == f"name.{synth2}": > + r.answer.append(dns.rrset.from_text(qname, ttl, IN, CNAME, "name.")) > + r.answer.append(dns.rrset.from_text(synth2, ttl, IN, DNAME, ".")) > + elif qname == "ns3.example.dname.": > + # This and the next two code branches referring to the > "example.dname" > + # zone are necessary for the resolver variant of the CVE-2021-25215 > + # regression test to work. A named instance cannot be used for > + # serving the DNAME records below as a version of BIND vulnerable to > + # CVE-2021-25215 would crash while answering the queries asked by > + # the tested resolver. > + if qtype == "A": > + r.answer.append(dns.rrset.from_text(qname, ttl, IN, A, ip4)) > + elif qtype == "AAAA": > + r.authority.append( > + dns.rrset.from_text("example.dname.", ttl, IN, SOA, ". . 0 0 > 0 0 0") > + ) > + elif qname == "self.example.self..example.dname.": > + r.answer.append( > + dns.rrset.from_text("self.example.dname.", ttl, IN, DNAME, > "dname.") > + ) > + r.answer.append( > + dns.rrset.from_text(qname, ttl, IN, CNAME, "self.example.dname.") > + ) > + elif qname == "self.example.dname.": > + if qtype == "DNAME": > + r.answer.append(dns.rrset.from_text(qname, ttl, IN, DNAME, > "dname.")) > + else: > + rcode = "REFUSED" > + > + r.flags |= dns.flags.AA > + r.use_edns() > + return r.to_wire() > + > + > +def sigterm(signum, frame): > + print("Shutting down now...") > + os.remove("ans.pid") > + running = False > + sys.exit(0) > + > + > +############################################################################ > +# Main > +# > +# Set up responder and control channel, open the pid file, and start > +# the main loop, listening for queries on the query channel or commands > +# on the control channel and acting on them. > +############################################################################ > +ip4 = "10.53.0.3" > +ip6 = "fd92:7065:b8e:ffff::3" > + > +try: > + port = int(os.environ["PORT"]) > +except: > + port = 5300 > + > +query4_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) > +query4_udp.bind((ip4, port)) > + > +query4_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) > +query4_tcp.bind((ip4, port)) > +query4_tcp.listen(1) > +query4_tcp.settimeout(1) > + > +havev6 = True > +try: > + query6_udp = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) > + try: > + query6_udp.bind((ip6, port)) > + except: > + query6_udp.close() > + havev6 = False > + > + query6_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) > + try: > + query6_tcp.bind((ip4, port)) > + query6_tcp.listen(1) > + query6_tcp.settimeout(1) > + except: > + query6_tcp.close() > + havev6 = False > +except: > + havev6 = False > + > +signal.signal(signal.SIGTERM, sigterm) > + > +f = open("ans.pid", "w") > +pid = os.getpid() > +print(pid, file=f) > +f.close() > + > +running = True > + > +print("Listening on %s port %d" % (ip4, port)) > +if havev6: > + print("Listening on %s port %d" % (ip6, port)) > +print("Ctrl-c to quit") > + > +if havev6: > + input = [query4_udp, query4_tcp, query6_udp, query6_tcp] > +else: > + input = [query4_udp, query4_tcp] > + > +while running: > + try: > + inputready, outputready, exceptready = select.select(input, [], []) > + except select.error as e: > + break > + except socket.error as e: > + break > + except KeyboardInterrupt: > + break > + > + for s in inputready: > + if s == query4_udp or s == query6_udp: > + print("Query received on %s" % (ip4 if s == query4_udp else ip6)) > + # Handle incoming queries > + msg = s.recvfrom(65535) > + rsp = create_response(msg[0]) > + if rsp: > + s.sendto(rsp, msg[1]) > + elif s == query4_tcp or s == query6_tcp: > + try: > + conn, _ = s.accept() > + if s == query4_tcp or s == query6_tcp: > + print( > + "TCP Query received on %s" % (ip4 if s == query4_tcp > else ip6), > + end=" ", > + ) > + # get TCP message length > + msg = conn.recv(2) > + if len(msg) != 2: > + print("couldn't read TCP message length") > + continue > + length = struct.unpack(">H", msg[:2])[0] > + msg = conn.recv(length) > + if len(msg) != length: > + print("couldn't read TCP message") > + continue > + rsp = create_response(msg) > + if rsp: > + conn.send(struct.pack(">H", len(rsp))) > + conn.send(rsp) > + conn.close() > + except socket.error as e: > + print("error: %s" % str(e)) > + if not running: > + break > diff --git a/bin/tests/system/chain/ans4/ans.py > b/bin/tests/system/chain/ans4/ans.py > index 839067faa5e..66f0193caca 100755 > --- a/bin/tests/system/chain/ans4/ans.py > +++ b/bin/tests/system/chain/ans4/ans.py > @@ -316,16 +316,30 @@ try: > except: > ctrlport = 5300 > > -query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) > -query4_socket.bind((ip4, port)) > +query4_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) > +query4_udp.bind((ip4, port)) > + > +query4_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) > +query4_tcp.bind((ip4, port)) > +query4_tcp.listen(1) > +query4_tcp.settimeout(1) > > havev6 = True > try: > - query6_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) > + query6_udp = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) > + try: > + query6_udp.bind((ip6, port)) > + except: > + query6_udp.close() > + havev6 = False > + > + query6_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) > try: > - query6_socket.bind((ip6, port)) > + query6_tcp.bind((ip4, port)) > + query6_tcp.listen(1) > + query6_tcp.settimeout(1) > except: > - query6_socket.close() > + query6_tcp.close() > havev6 = False > except: > havev6 = False > @@ -350,9 +364,9 @@ print("Control channel on %s port %d" % (ip4, ctrlport)) > print("Ctrl-c to quit") > > if havev6: > - input = [query4_socket, query6_socket, ctrl_socket] > + input = [query4_udp, query4_tcp, query6_udp, query6_tcp, ctrl_socket] > else: > - input = [query4_socket, ctrl_socket] > + input = [query4_udp, query4_tcp, ctrl_socket] > > while running: > try: > @@ -375,12 +389,37 @@ while running: > break > ctl_channel(msg) > conn.close() > - if s == query4_socket or s == query6_socket: > - print("Query received on %s" % (ip4 if s == query4_socket else > ip6)) > + elif s == query4_udp or s == query6_udp: > + print("Query received on %s" % (ip4 if s == query4_udp else ip6)) > # Handle incoming queries > msg = s.recvfrom(65535) > rsp = create_response(msg[0]) > if rsp: > s.sendto(rsp, msg[1]) > + elif s == query4_tcp or s == query6_tcp: > + try: > + conn, _ = s.accept() > + if s == query4_tcp or s == query6_tcp: > + print( > + "TCP Query received on %s" % (ip4 if s == query4_tcp > else ip6), > + end=" ", > + ) > + # get TCP message length > + msg = conn.recv(2) > + if len(msg) != 2: > + print("couldn't read TCP message length") > + continue > + length = struct.unpack(">H", msg[:2])[0] > + msg = conn.recv(length) > + if len(msg) != length: > + print("couldn't read TCP message") > + continue > + rsp = create_response(msg) > + if rsp: > + conn.send(struct.pack(">H", len(rsp))) > + conn.send(rsp) > + conn.close() > + except socket.error as e: > + print("error: %s" % str(e)) > if not running: > break > diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h > index fe51fcfe24b..280d872d208 100644 > --- a/lib/dns/include/dns/message.h > +++ b/lib/dns/include/dns/message.h > @@ -236,6 +236,7 @@ struct dns_message { > unsigned int tkey : 1; > unsigned int rdclass_set : 1; > unsigned int fuzzing : 1; > + unsigned int has_dname : 1; > > unsigned int opt_reserved; > unsigned int sig_reserved; > @@ -1457,4 +1458,11 @@ dns_message_clonebuffer(dns_message_t *msg); > * \li msg be a valid message. > */ > > +bool > +dns_message_hasdname(dns_message_t *msg); > +/*%< > + * Return whether a DNAME was detected in the ANSWER section of a QUERY > + * message when it was parsed. > + */ > + > ISC_LANG_ENDDECLS > diff --git a/lib/dns/message.c b/lib/dns/message.c > index e23baf7e09e..225c9d7576f 100644 > --- a/lib/dns/message.c > +++ b/lib/dns/message.c > @@ -442,6 +442,7 @@ msginit(dns_message_t *m) { > m->cc_bad = 0; > m->tkey = 0; > m->rdclass_set = 0; > + m->has_dname = 0; > m->querytsig = NULL; > m->indent.string = "\t"; > m->indent.count = 0; > @@ -1727,6 +1728,11 @@ getsection(isc_buffer_t *source, dns_message_t *msg, > dns_decompress_t *dctx, > */ > msg->tsigname->attributes |= DNS_NAMEATTR_NOCOMPRESS; > free_name = false; > + } else if (rdtype == dns_rdatatype_dname && > + sectionid == DNS_SECTION_ANSWER && > + msg->opcode == dns_opcode_query) > + { > + msg->has_dname = 1; > } > rdataset = NULL; > > @@ -4773,3 +4779,9 @@ dns_message_clonebuffer(dns_message_t *msg) { > msg->free_query = 1; > } > } > + > +bool > +dns_message_hasdname(dns_message_t *msg) { > + REQUIRE(DNS_MESSAGE_VALID(msg)); > + return msg->has_dname; > +} > diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c > index 168fcc5e808..a823f5a7051 100644 > --- a/lib/dns/resolver.c > +++ b/lib/dns/resolver.c > @@ -758,6 +758,7 @@ typedef struct respctx { > bool get_nameservers; /* get a new NS rrset at > * zone cut? */ > bool resend; /* resend this query? */ > + bool secured; /* message was signed or had a valid cookie */ > bool nextitem; /* invalid response; keep > * listening for the correct one */ > bool truncated; /* response was truncated */ > @@ -7900,6 +7901,47 @@ betterreferral(respctx_t *rctx) { > return (false); > } > > +static bool > +rctx_need_tcpretry(respctx_t *rctx) { > + resquery_t *query = rctx->query; > + if ((rctx->retryopts & DNS_FETCHOPT_TCP) != 0) { > + /* TCP is already in the retry flags */ > + return false; > + } > + > + /* > + * If the message was secured, no need to continue. > + */ > + if (rctx->secured) { > + return false; > + } > + > + /* > + * Currently the only extra reason why we might need to > + * retry a UDP response over TCP is a DNAME in the message. > + */ > + if (dns_message_hasdname(query->rmessage)) { > + return true; > + } > + > + return false; > +} > + > +static isc_result_t > +rctx_tcpretry(respctx_t *rctx) { > + /* > + * Do we need to retry a UDP response over TCP? > + */ > + if (rctx_need_tcpretry(rctx)) { > + rctx->retryopts |= DNS_FETCHOPT_TCP; > + rctx->resend = true; > + rctx_done(rctx, ISC_R_SUCCESS); > + return ISC_R_COMPLETE; > + } > + > + return ISC_R_SUCCESS; > +} > + > /* > * resquery_response(): > * Handles responses received in response to iterative queries sent by > @@ -8058,6 +8100,11 @@ resquery_response(isc_task_t *task, isc_event_t > *event) { > break; > } > > + /* > + * The dispatcher should ensure we only get responses with QR set. > + */ > + INSIST((query->rmessage->flags & DNS_MESSAGEFLAG_QR) != 0); > + > /* > * If the message is signed, check the signature. If not, this > * returns success anyway. > @@ -8075,9 +8122,16 @@ resquery_response(isc_task_t *task, isc_event_t > *event) { > } > > /* > - * The dispatcher should ensure we only get responses with QR set. > + * Remember whether this message was signed or had a > + * valid client cookie; if not, we may need to retry over > + * TCP later. > */ > - INSIST((query->rmessage->flags & DNS_MESSAGEFLAG_QR) != 0); > + if (query->rmessage->cc_ok || query->rmessage->tsig != NULL || > + query->rmessage->sig0 != NULL) > + { > + rctx.secured = true; > + } > + > /* > * INSIST() that the message comes from the place we sent it to, > * since the dispatch code should ensure this. > @@ -8091,10 +8145,7 @@ resquery_response(isc_task_t *task, isc_event_t > *event) { > * This may be a misconfigured anycast server or an attempt to send > * a spoofed response. Skip if we have a valid tsig. > */ > - if (dns_message_gettsig(query->rmessage, NULL) == NULL && > - !query->rmessage->cc_ok && !query->rmessage->cc_bad && > - (rctx.retryopts & DNS_FETCHOPT_TCP) == 0) > - { > + if (!rctx.secured && (rctx.retryopts & DNS_FETCHOPT_TCP) == 0) { > unsigned char cookie[COOKIE_BUFFER_SIZE]; > if (dns_adb_getcookie(fctx->adb, query->addrinfo, cookie, > sizeof(cookie)) > CLIENT_COOKIE_SIZE) > @@ -8120,6 +8171,17 @@ resquery_response(isc_task_t *task, isc_event_t > *event) { > */ > } > > + /* > + * Check whether we need to retry over TCP for some other reason. > + */ > + result = rctx_tcpretry(&rctx); > + if (result == ISC_R_COMPLETE) { > + return; > + } > + > + /* > + * Check for EDNS issues. > + */ > rctx_edns(&rctx); > > /* > @@ -8848,8 +8910,8 @@ rctx_answer_positive(respctx_t *rctx) { > } > > /* > - * Cache records in the authority section, if > - * there are any suitable for caching. > + * Cache records in the authority section, if there are > + * any suitable for caching. > */ > rctx_authority_positive(rctx); > > @@ -9221,14 +9283,14 @@ rctx_answer_dname(respctx_t *rctx) { > > /* > * rctx_authority_positive(): > - * Examine the records in the authority section (if there are any) for a > - * positive answer. We expect the names for all rdatasets in this section > - * to be subdomains of the domain being queried; any that are not are > - * skipped. We expect to find only *one* owner name; any names > - * after the first one processed are ignored. We expect to find only > - * rdatasets of type NS, RRSIG, or SIG; all others are ignored. Whatever > - * remains can be cached at trust level authauthority or additional > - * (depending on whether the AA bit was set on the answer). > + * If a positive answer was received over TCP or secured with a cookie > + * or TSIG, examine the authority section. We expect names for all > + * rdatasets in this section to be subdomains of the domain being queried; > + * any that are not are skipped. We expect to find only *one* owner name; > + * any names after the first one processed are ignored. We expect to find > + * only rdatasets of type NS; all others are ignored. Whatever remains can > + * be cached at trust level authauthority or additional (depending on > + * whether the AA bit was set on the answer). > */ > static void > rctx_authority_positive(respctx_t *rctx) { > @@ -9236,6 +9298,11 @@ rctx_authority_positive(respctx_t *rctx) { > bool done = false; > isc_result_t result; > > + /* If it's spoofable, don't cache it. */ > + if (!rctx->secured && (rctx->query->options & DNS_FETCHOPT_TCP) == 0) { > + return; > + } > + > result = dns_message_firstname(rctx->query->rmessage, > DNS_SECTION_AUTHORITY); > while (!done && result == ISC_R_SUCCESS) { > -- > GitLab > Cheers, -- Santiago
signature.asc
Description: PGP signature
