- Fix CVE-2026-40691, Packet of death for DNSCrypt over TCP. Thanks

to Qifan Zhang, Palo Alto Networks, for the report. In addition,
  thanks to Trung Nguyen (@everping) of CyStack, for also reporting
  this issue.
This commit is contained in:
W.C.A. Wijngaards
2026-07-22 10:09:50 +02:00
parent 01dfd2f466
commit f54e0791ba
6 changed files with 44 additions and 21 deletions
+9 -1
View 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
+12 -2
View 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;
}
+2 -1
View 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.
+9 -8
View 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
@@ -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
+3 -1
View 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