]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix CVE-2026-32665, Remote DNS-over-QUIC denial of
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 22 Jul 2026 08:09:26 +0000 (10:09 +0200)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 22 Jul 2026 08:09:26 +0000 (10:09 +0200)
  service due to `quic-size` budget bypass. Thanks to N0zoM1z0
  (https://github.com/N0zoM1z0) for the report. In addition, thanks to
  Kunta Chu, Kaihua Wang, and Jianjun Chen from Tsinghua University,
  for also reporting this issue. In addition, thanks to Qifan Zhang,
  Palo Alto Networks, for also reporting this issue. In addition,
  thanks to Xuanchao Xie, for also reporting this issue.

services/listen_dnsport.c

index a08a5352ecff82373647b002558ddc86989fd770..caae7fcc4b09fc710e1d6c192520fcdf5931fc87 100644 (file)
@@ -3964,7 +3964,8 @@ doq_stream_close(struct doq_conn* conn, struct doq_stream* stream,
 
 /** doq stream pick up answer data from buffer */
 static int
-doq_stream_pickup_answer(struct doq_stream* stream, struct sldns_buffer* buf)
+doq_stream_pickup_answer(struct doq_conn* conn, struct doq_stream* stream,
+       struct sldns_buffer* buf)
 {
        stream->is_answer_available = 1;
        if(stream->out) {
@@ -3974,6 +3975,11 @@ doq_stream_pickup_answer(struct doq_stream* stream, struct sldns_buffer* buf)
        }
        stream->nwrite = 0;
        stream->outlen = sldns_buffer_limit(buf);
+       if(!doq_table_quic_size_available(conn->doq_socket->table,
+               conn->doq_socket->cfg, stream->outlen)) {
+               verbose(VERB_ALGO, "doq stream: no space for reply length");
+               return 0;
+       }
        /* For quic the output bytes have to stay allocated and available,
         * for potential resends, until the remote end has acknowledged them.
         * This includes the tcplen start uint16_t, in outlen_wire. */
@@ -4000,7 +4006,7 @@ doq_stream_send_reply(struct doq_conn* conn, struct doq_stream* stream,
        if(stream->out)
                doq_table_quic_size_subtract(conn->doq_socket->table,
                        stream->outlen);
-       if(!doq_stream_pickup_answer(stream, buf))
+       if(!doq_stream_pickup_answer(conn, stream, buf))
                return 0;
        doq_table_quic_size_add(conn->doq_socket->table, stream->outlen);
        doq_stream_on_write_list(conn, stream);
@@ -4011,13 +4017,19 @@ doq_stream_send_reply(struct doq_conn* conn, struct doq_stream* stream,
 /** doq stream data length has completed, allocations can be done. False on
  * allocation failure. */
 static int
-doq_stream_datalen_complete(struct doq_stream* stream, struct doq_table* table)
+doq_stream_datalen_complete(struct doq_conn* conn, struct doq_stream* stream,
+       struct doq_table* table)
 {
        if(stream->inlen > 1024*1024) {
                log_err("doq stream in length too large %d",
                        (int)stream->inlen);
                return 0;
        }
+       if(!doq_table_quic_size_available(table, conn->doq_socket->cfg,
+               stream->inlen)) {
+               verbose(VERB_ALGO, "doq stream: no space for query length");
+               return 0;
+       }
        stream->in = calloc(1, stream->inlen);
        if(!stream->in) {
                log_err("doq could not read stream, calloc failed: "
@@ -4078,8 +4090,9 @@ doq_stream_data_complete(struct doq_conn* conn, struct doq_stream* stream)
 
 /** doq receive data for a stream, more bytes of the incoming data */
 static int
-doq_stream_recv_data(struct doq_stream* stream, const uint8_t* data,
-       size_t datalen, int* recv_done, struct doq_table* table)
+doq_stream_recv_data(struct doq_conn* conn, struct doq_stream* stream,
+       const uint8_t* data, size_t datalen, int* recv_done,
+       struct doq_table* table)
 {
        int got_data = 0;
        /* read the tcplength uint16_t at the start */
@@ -4100,7 +4113,7 @@ doq_stream_recv_data(struct doq_stream* stream, const uint8_t* data,
                if(stream->nread == 2) {
                        /* the initial length value is completed */
                        stream->inlen = ntohs(tcplen);
-                       if(!doq_stream_datalen_complete(stream, table))
+                       if(!doq_stream_datalen_complete(conn, stream, table))
                                return 0;
                } else {
                        /* store for later */
@@ -4316,8 +4329,7 @@ doq_stream_open_cb(ngtcp2_conn* ATTR_UNUSED(conn), int64_t stream_id,
                verbose(VERB_ALGO, "doq: stream with this id already exists");
                return 0;
        }
-       if(stream_id != 0 && stream_id != 4 && /* allow one stream on a new connection */
-               !doq_table_quic_size_available(doq_conn->doq_socket->table,
+       if(!doq_table_quic_size_available(doq_conn->doq_socket->table,
                doq_conn->doq_socket->cfg, sizeof(*stream)
                + 100 /* estimated query in */
                + 512 /* estimated response out */
@@ -4375,8 +4387,8 @@ doq_recv_stream_data_cb(ngtcp2_conn* ATTR_UNUSED(conn), uint32_t flags,
                return 0;
        }
        if(datalen != 0) {
-               if(!doq_stream_recv_data(stream, data, datalen, &recv_done,
-                       doq_conn->doq_socket->table))
+               if(!doq_stream_recv_data(doq_conn, stream, data, datalen,
+                       &recv_done, doq_conn->doq_socket->table))
                        return NGTCP2_ERR_CALLBACK_FAILURE;
        }
        if((flags&NGTCP2_STREAM_DATA_FLAG_FIN)!=0) {