]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Disclose Negative Trust Anchors with Extended DNS Error 33
authorOndřej Surý <ondrej@sury.org>
Mon, 20 Jul 2026 08:45:50 +0000 (10:45 +0200)
committerOndřej Surý <ondrej@sury.org>
Mon, 20 Jul 2026 10:08:33 +0000 (12:08 +0200)
A Negative Trust Anchor (RFC 7646) makes a validating resolver treat an
otherwise-secure name as insecure, but there was no in-band way for a
client to tell that an answer which should have failed DNSSEC validation
was returned because an NTA was in place.

Register Extended DNS Error INFO-CODE 33, "Negative Trust Anchor"
(draft-farrokhi-dnsop-ede-nta), and attach it to a response whenever a
covering NTA suppresses validation for the queried name.

lib/dns/ede.c
lib/dns/include/dns/ede.h
lib/dns/resolver.c
lib/isc/include/isc/util.h

index 48fc153188696647bc901b18b122f645d7410b02..7dc9075a84c6767d2a0c64bcc2dbff939607d7fa 100644 (file)
@@ -13,6 +13,8 @@
 
 /*! \file */
 
+#include <inttypes.h>
+
 #include <isc/mem.h>
 #include <isc/util.h>
 
 #define DNS_EDE_VALID(v) ISC_MAGIC_VALID(v, DNS_EDE_MAGIC)
 
 static bool
-dns__ede_checkandupdateedeused(dns_edectx_t *edectx, uint16_t code) {
-       if (edectx->edeused & (1 << code)) {
+dns__ede_checkandupdateedeused(dns_edectx_t *edectx, dns_edecode_t code) {
+       if (edectx->edeused & (UINT64_C(1) << code)) {
                return true;
        }
 
-       edectx->edeused |= 1 << code;
+       edectx->edeused |= UINT64_C(1) << code;
        return false;
 }
 
index a9470306cb6e496dd795153ff0b76df092c0f30a..df96237dfa3ea61079eeb3ccfc06aec9cad05a46 100644 (file)
 
 #pragma once
 
+#include <inttypes.h>
+
 #include <isc/mem.h>
+#include <isc/util.h>
 
 #include <dns/message.h>
 
 /*%< EDNS0 extended DNS errors */
-enum {
+typedef enum ISC_FIXED_ENUM(dns_edecode, uint16_t) {
+       // typedef enum ISC_FIXED_ENUM(dns_edecode, uint16_t) {
        DNS_EDE_OTHER = 0,                /*%< Other Error */
        DNS_EDE_DNSKEYALG = 1,            /*%< Unsupported DNSKEY Algorithm */
        DNS_EDE_DSDIGESTTYPE = 2,         /*%< Unsupported DS Digest Type */
@@ -44,8 +48,13 @@ enum {
        DNS_EDE_NOREACHABLEAUTH = 22,     /*%< No Reachable Authority */
        DNS_EDE_NETWORKERROR = 23,        /*%< Network Error */
        DNS_EDE_INVALIDDATA = 24,         /*%< Invalid Data */
-       DNS_EDE_MAX_CODE
-};
+       DNS_EDE_NTA = 33,                 /*%< Negative Trust Anchor */
+       DNS_EDE_MAX_CODE,
+       DNS_EDE_ENFORCE = UINT16_MAX, /*%< Enforce uint16_t size */
+} dns_edecode_t;
+
+STATIC_ASSERT(sizeof(dns_edecode_t) == sizeof(uint16_t),
+             "dns_edecode_t is not uint16_t sized");
 
 /*
  * From RFC 8914:
@@ -62,7 +71,7 @@ struct dns_edectx {
        int            magic;
        isc_mem_t     *mctx;
        dns_ednsopt_t *ede[DNS_EDE_MAX_ERRORS];
-       uint32_t       edeused;
+       uint64_t       edeused;
        size_t         nextede;
 };
 /*%<
@@ -74,6 +83,13 @@ struct dns_edectx {
  * the response client message.
  */
 
+STATIC_ASSERT(DNS_EDE_MAX_CODE <=
+                     CHAR_BIT * sizeof(((dns_edectx_t *){ NULL })->edeused),
+             "DNS_EDE_MAX_CODE does not fit in the edeused bitmap");
+/*
+ * Make sure we can fit the currently supported EDE codes in the edeused bitmap.
+ */
+
 void
 dns_ede_init(isc_mem_t *mctx, dns_edectx_t *edectx);
 /*%<
index a094eeb430bfd10b799802c867705e8a1d36037a..b79e0821b859962275ad2e26e667d71ab20be7e6 100644 (file)
@@ -2446,9 +2446,10 @@ compute_cc(const resquery_t *query, uint8_t *cookie, const size_t len) {
 
 static bool
 issecuredomain(fetchctx_t *fctx, const dns_name_t *name, dns_rdatatype_t type,
-              isc_stdtime_t now, bool *ntap) {
+              isc_stdtime_t now) {
        dns_name_t suffix;
        unsigned int labels;
+       bool secure_domain, nta = false;
 
        /*
         * For DS variants we need to check fom the parent domain,
@@ -2463,8 +2464,22 @@ issecuredomain(fetchctx_t *fctx, const dns_name_t *name, dns_rdatatype_t type,
                name = &suffix;
        }
 
-       return dns_view_issecuredomain(fctx->res->view, name, now,
-                                      CHECKNTA(fctx), ntap);
+       secure_domain = dns_view_issecuredomain(fctx->res->view, name, now,
+                                               CHECKNTA(fctx), &nta);
+
+       /*
+        * A covering negative trust anchor suppressed DNSSEC validation for
+        * an otherwise secure name (RFC 7646). Disclose that to the client
+        * via an Extended DNS Error (draft-farrokhi-dnsop-ede-nta). Duplicate
+        * codes are coalesced by dns_ede_add(), so this is emitted at most
+        * once per fetch.
+        */
+       if (nta) {
+               dns_ede_add(&fctx->edectx, DNS_EDE_NTA,
+                           "Negative Trust Anchor applied (RFC 7646)");
+       }
+
+       return secure_domain;
 }
 
 static isc_result_t
@@ -6343,8 +6358,7 @@ rctx_cachename(respctx_t *rctx, dns_message_t *message, dns_name_t *name) {
        /*
         * Is DNSSEC validation required for this name?
         */
-       bool secure_domain = issecuredomain(fctx, name, fctx->type, rctx->now,
-                                           NULL);
+       bool secure_domain = issecuredomain(fctx, name, fctx->type, rctx->now);
        bool need_validation = secure_domain &&
                               ((fctx->options & DNS_FETCHOPT_NOVALIDATE) == 0);
 
@@ -6592,8 +6606,7 @@ rctx_ncache(respctx_t *rctx) {
        /*
         * Is DNSSEC validation required for this name?
         */
-       bool secure_domain = issecuredomain(fctx, name, fctx->type, rctx->now,
-                                           NULL);
+       bool secure_domain = issecuredomain(fctx, name, fctx->type, rctx->now);
        bool need_validation = secure_domain &&
                               ((fctx->options & DNS_FETCHOPT_NOVALIDATE) == 0);
 
@@ -9418,7 +9431,7 @@ rctx_authority_dnssec(respctx_t *rctx) {
 
                                secure_domain = issecuredomain(fctx, name,
                                                               dns_rdatatype_ds,
-                                                              fctx->now, NULL);
+                                                              fctx->now);
                                if (secure_domain) {
                                        rdataset->trust =
                                                dns_trust_pending_answer;
index ddfce87c32489cd5fed2826d8eb55aa8544117dd..21abcccb2bf44e5a0560a2d6d6b8ce806b319b22 100644 (file)
@@ -313,3 +313,15 @@ mock_assert(const int result, const char *const expression,
 #else /* __has_builtin(__builtin_types_compatible_p) */
 #define ISC_TYPES_COMPATIBLE(x, y) 1
 #endif /* __has_builtin(__builtin_types_compatible_p) */
+
+/* clang-format off */
+/*
+ * This needs some extra plumbing for non-C23 compilers, see
+ * lib/dns/include/dns/ede.h:dns_edecode_t for example.
+ */
+#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
+#define ISC_FIXED_ENUM(name, type) name: type
+#else
+#define ISC_FIXED_ENUM(name, type) __attribute__((__packed__)) name
+#endif
+/* clang-format on */