]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix CVE-2026-40691, Packet of death for DNSCrypt over TCP. Thanks
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 22 Jul 2026 08:09:50 +0000 (10:09 +0200)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 22 Jul 2026 08:09:50 +0000 (10:09 +0200)
  to Qifan Zhang, Palo Alto Networks, for the report. In addition,
  thanks to Trung Nguyen (@everping) of CyStack, for also reporting
  this issue.

daemon/worker.c
dnscrypt/dnscrypt.c
dnscrypt/dnscrypt.h
testdata/dnscrypt_cert.tdir/dnscrypt_cert.test
testdata/dnscrypt_cert_chacha.tdir/dnscrypt_cert_chacha.test
util/netevent.c

index a5dd9bc028e8290636ebdbd57ce11b47dd054429..b4e50b3452541135f4a7ac085d7897304614bbc5 100644 (file)
@@ -1550,6 +1550,7 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
                                return 0;
                        }
                        query_error(c->buffer, LDNS_RCODE_FORMERR, 0);
+                       sldns_buffer_copy(c->dnscrypt_buffer, c->buffer);
                        return 1;
                }
                dname_str(qinfo.qname, buf);
@@ -1568,6 +1569,7 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
                        query_error(c->buffer, LDNS_RCODE_SERVFAIL,
                                qinfo.qname_len);
                        worker->stats.num_query_dnscrypt_cleartext++;
+                       sldns_buffer_copy(c->dnscrypt_buffer, c->buffer);
                        return 1;
                }
                worker->stats.num_query_dnscrypt_cert++;
@@ -1828,7 +1830,13 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
                server_stats_insquery(&worker->stats, c, qinfo.qtype,
                        qinfo.qclass, &edns, repinfo);
        if(c->type != comm_udp)
+#ifdef USE_DNSCRYPT
+               edns.udp_size = (c->dnscrypt && repinfo->is_dnscrypted)
+                       ? sldns_buffer_capacity(c->buffer) - DNSCRYPT_REPLY_HEADER_SIZE
+                       : 65535;
+#else
                edns.udp_size = 65535; /* max size for TCP replies */
+#endif
        if(qinfo.qclass == LDNS_RR_CLASS_CH && answer_chaos(worker, &qinfo,
                &edns, repinfo, c->buffer)) {
                regional_free_all(worker->scratchpad);
@@ -2112,7 +2120,7 @@ send_reply_rc:
                }
        }
 #ifdef USE_DNSCRYPT
-       if(!dnsc_handle_uncurved_request(repinfo)) {
+       if(!dnsc_handle_uncurved_request(repinfo, c->buffer)) {
                return 0;
        }
 #endif
index 173484cdf0b10eb9a75e48df85c756b1494e4718..08f9dcb8cea769fb92b1223550b434e8174c1e2c 100644 (file)
@@ -474,10 +474,18 @@ dnscrypt_server_curve(const dnsccert *cert,
     uint8_t *const buf = sldns_buffer_begin(buffer);
     size_t len = sldns_buffer_limit(buffer);
 
+    if(len + DNSCRYPT_REPLY_HEADER_SIZE > sldns_buffer_capacity(buffer))
+       return -1;
+    sldns_buffer_clear(buffer);
+
     if(udp){
         if (max_len > max_reply_size)
             max_len = max_reply_size;
     }
+    if(max_len > sldns_buffer_capacity(buffer))
+       max_len = sldns_buffer_capacity(buffer);
+    if(max_len > 65535)
+           max_len = 65535;
 
 
     memcpy(nonce, client_nonce, crypto_box_HALF_NONCEBYTES);
@@ -520,6 +528,7 @@ dnscrypt_server_curve(const dnsccert *cert,
                           DNSCRYPT_MAGIC_HEADER_LEN,
                           nonce,
                           crypto_box_NONCEBYTES);
+    sldns_buffer_flip(buffer);
     sldns_buffer_set_limit(buffer, len + DNSCRYPT_REPLY_HEADER_SIZE);
     return 0;
 }
@@ -912,12 +921,13 @@ dnsc_handle_curved_request(struct dnsc_env* dnscenv,
 }
 
 int
-dnsc_handle_uncurved_request(struct comm_reply *repinfo)
+dnsc_handle_uncurved_request(struct comm_reply *repinfo,
+       struct sldns_buffer* buffer)
 {
     if(!repinfo->c->dnscrypt) {
         return 1;
     }
-    sldns_buffer_copy(repinfo->c->dnscrypt_buffer, repinfo->c->buffer);
+    sldns_buffer_copy(repinfo->c->dnscrypt_buffer, buffer);
     if(!repinfo->is_dnscrypted) {
         return 1;
     }
index b0da9b732681fce5a815ca445cd073475d17e931..998237d48e83cfe2eadfa7592f3186247c26a43c 100644 (file)
@@ -128,7 +128,8 @@ int dnsc_handle_curved_request(struct dnsc_env* dnscenv,
  * \return 0 in case of failure.
  */
 
-int dnsc_handle_uncurved_request(struct comm_reply *repinfo);
+int dnsc_handle_uncurved_request(struct comm_reply *repinfo,
+       struct sldns_buffer* buffer);
 
 /**
  * Computes the size of the shared secret cache entry.
index fdb88e8f949040a29707227cf1b028077566dac0..4631d02e13abb0203979dc062d8686252d64ca5f 100644 (file)
@@ -9,20 +9,21 @@ PRE="../.."
 
 # do the test
 
-# Query plain request over DNSCrypt channel get closed
-# We use TCP to avoid hanging on waiting for UDP.
-# We expect `outfile` to contain no DNS payload
-echo "> dig TCP www.example.com. DNSCrypt port"
-dig +tcp @127.0.0.1 -p $DNSCRYPT_PORT www.example.com. A | tee outfile
+# Query plain request over DNSCrypt.
+# This used to close the channel; now it returns SERVFAIL.
+# Old: We use TCP to avoid hanging on waiting for UDP.
+#      We expect `outfile` to contain no DNS payload
+echo "> dig www.example.com. DNSCrypt port"
+dig @127.0.0.1 -p $DNSCRYPT_PORT www.example.com. A | tee outfile
 echo "> cat logfiles"
 cat fwd.log
 cat unbound.log
 echo "> check answer"
-if grep "QUESTION SECTION" outfile; then
+if grep "SERVFAIL" outfile; then
+       echo "OK"
+else
        echo "NOK"
        exit 1
-else
-       echo "OK"
 fi
 
 
index 2db073ad6a40ce38a869eba50096bf9b502e1610..a92f196f5787d58539a783df5274af540341d8fa 100644 (file)
@@ -9,20 +9,21 @@ PRE="../.."
 
 # do the test
 
-# Query plain request over DNSCrypt channel get closed
-# We use TCP to avoid hanging on waiting for UDP.
-# We expect `outfile` to contain no DNS payload
-echo "> dig TCP www.example.com. DNSCrypt port"
-dig +tcp @127.0.0.1 -p $DNSCRYPT_PORT www.example.com. A | tee outfile
+# Query plain request over DNSCrypt.
+# This used to close the channel; now it returns SERVFAIL.
+# Old: We use TCP to avoid hanging on waiting for UDP.
+#      We expect `outfile` to contain no DNS payload
+echo "> dig www.example.com. DNSCrypt port"
+dig @127.0.0.1 -p $DNSCRYPT_PORT www.example.com. A | tee outfile
 echo "> cat logfiles"
 cat fwd.log
 cat unbound.log
 echo "> check answer"
-if grep "QUESTION SECTION" outfile; then
+if grep "SERVFAIL" outfile; then
+       echo "OK"
+else
        echo "NOK"
        exit 1
-else
-       echo "OK"
 fi
 
 
index e1480dc228108b5820f1975fd1317b554d4ed143..58938a2201159a720e0481514cba06e29e508264 100644 (file)
@@ -6677,7 +6677,9 @@ comm_point_send_reply(struct comm_reply *repinfo)
        log_assert(repinfo && repinfo->c);
 #ifdef USE_DNSCRYPT
        buffer = repinfo->c->dnscrypt_buffer;
-       if(!dnsc_handle_uncurved_request(repinfo)) {
+       if(!dnsc_handle_uncurved_request(repinfo,
+               repinfo->c->tcp_req_info?
+               repinfo->c->tcp_req_info->spool_buffer:repinfo->c->buffer)) {
                return;
        }
 #else