From b247dbb3506ef628a683d184fbc6a99fad45ed94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= Date: Wed, 4 Mar 2026 10:46:58 +0100 Subject: [PATCH] Validate DNS message CLASS early in request processing Reject requests with unsupported or misused CLASS values before further processing. Only IN, CH, HS, RESERVED0 (for DNS Cookies), ANY (for TKEY negotiation), and NONE (for DNS UPDATE) are accepted; all other classes return NOTIMP. Misuse of NONE or ANY outside their allowed contexts returns FORMERR. This adds further protection against bugs of the same general class as YWH-PGM40640-70 and YWH-PGM40640-73. (cherry picked from commit 0a687451505037e9f9a850c9cb113aed4995b03f) --- bin/tests/system/unknown/tests.sh | 17 +++++++---- lib/ns/client.c | 51 ++++++++++++++++++++++++++----- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/bin/tests/system/unknown/tests.sh b/bin/tests/system/unknown/tests.sh index eb61f21f28..cbc2943f17 100644 --- a/bin/tests/system/unknown/tests.sh +++ b/bin/tests/system/unknown/tests.sh @@ -25,6 +25,11 @@ dig_cmd() { "$DIG" $DIGOPTS "$@" | grep -v '^;' } +dig_full() { + # shellcheck disable=SC2086 + "$DIG" $DIGOPTS "$@" +} + n=$((n + 1)) echo_i "querying for various representations of an IN A record ($n)" for i in 1 2 3 4 5 6 7 8 9 10 11 12; do @@ -81,8 +86,8 @@ n=$((n + 1)) echo_i "querying for various representations of a CLASS10 TYPE1 record ($n)" for i in 1 2; do ret=0 - dig_cmd +short @10.53.0.1 a$i.example a class10 >dig.out.$i.test$n - echo '\# 4 0A000001' | diff - dig.out.$i.test$n || ret=1 + dig_full @10.53.0.1 a$i.example a class10 >dig.out.$i.test$n + grep -q "NOTIMP" dig.out.$i.test$n || ret=1 if [ $ret != 0 ]; then echo_i "#$i failed" fi @@ -93,8 +98,8 @@ n=$((n + 1)) echo_i "querying for various representations of a CLASS10 TXT record ($n)" for i in 1 2 3 4; do ret=0 - dig_cmd +short @10.53.0.1 txt$i.example txt class10 >dig.out.$i.test$n - echo '"hello"' | diff - dig.out.$i.test$n || ret=1 + dig_full @10.53.0.1 txt$i.example txt class10 >dig.out.$i.test$n + grep -q "NOTIMP" dig.out.$i.test$n || ret=1 if [ $ret != 0 ]; then echo_i "#$i failed" fi @@ -105,8 +110,8 @@ n=$((n + 1)) echo_i "querying for various representations of a CLASS10 TYPE123 record ($n)" for i in 1 2; do ret=0 - dig_cmd +short @10.53.0.1 unk$i.example type123 class10 >dig.out.$i.test$n - echo '\# 1 00' | diff - dig.out.$i.test$n || ret=1 + dig_full @10.53.0.1 unk$i.example type123 class10 >dig.out.$i.test$n + grep -q "NOTIMP" dig.out.$i.test$n || ret=1 if [ $ret != 0 ]; then echo_i "#$i failed" fi diff --git a/lib/ns/client.c b/lib/ns/client.c index 4c538f5f9f..8680cd6c47 100644 --- a/lib/ns/client.c +++ b/lib/ns/client.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -2083,7 +2084,9 @@ ns__client_request(isc_nmhandle_t *handle, isc_result_t eresult, } } - if (client->message->rdclass == 0) { + char classbuf[DNS_RDATACLASS_FORMATSIZE]; + switch (client->message->rdclass) { + case dns_rdataclass_reserved0: if ((client->attributes & NS_CLIENTATTR_WANTCOOKIE) != 0 && client->message->opcode == dns_opcode_query && client->message->counts[DNS_SECTION_QUESTION] == 0U) @@ -2102,12 +2105,46 @@ ns__client_request(isc_nmhandle_t *handle, isc_result_t eresult, return; } + ns_client_dumpmessage(client, + "message class could not be determined"); + ns_client_error(client, notimp ? DNS_R_NOTIMP : DNS_R_FORMERR); + return; + case dns_rdataclass_in: + break; + case dns_rdataclass_chaos: + break; + case dns_rdataclass_hs: + break; + case dns_rdataclass_none: + if (client->message->opcode != dns_opcode_update) { + ns_client_dumpmessage(client, + "message class NONE can be only " + "used in DNS updates"); + ns_client_error(client, DNS_R_FORMERR); + return; + } + break; + case dns_rdataclass_any: + /* + * Required for TKEY negotiation. + */ + if (client->message->tkey == 0) { + ns_client_dumpmessage(client, + "message class ANY can be only " + "used for TKEY negotiation"); + ns_client_error(client, DNS_R_FORMERR); + return; + } + break; + default: + dns_rdataclass_format(client->message->rdclass, classbuf, + sizeof(classbuf)); + ns_client_dumpmessage(client, NULL); ns_client_log(client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(1), - "message class could not be determined"); - ns_client_dumpmessage(client, "message class could not be " - "determined"); - ns_client_error(client, notimp ? DNS_R_NOTIMP : DNS_R_FORMERR); + "invalid message class: %s", classbuf); + + ns_client_error(client, DNS_R_NOTIMP); return; } @@ -2140,7 +2177,7 @@ ns__client_request(isc_nmhandle_t *handle, isc_result_t eresult, ns_client_log(client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(1), "no matching view in class '%s'", classname); - ns_client_dumpmessage(client, "no matching view in class"); + ns_client_dumpmessage(client, NULL); ns_client_extendederror(client, DNS_EDE_PROHIBITED, NULL); ns_client_error(client, notimp ? DNS_R_NOTIMP : DNS_R_REFUSED); return; @@ -2781,7 +2818,7 @@ ns_client_dumpmessage(ns_client_t *client, const char *reason) { int len = 1024; isc_result_t result; - if (!isc_log_wouldlog(ns_lctx, ISC_LOG_DEBUG(1))) { + if (!isc_log_wouldlog(ns_lctx, ISC_LOG_DEBUG(1)) || reason == NULL) { return; }