]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix CVE-2026-41637, Degradation of resolution service from
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 22 Jul 2026 08:10:24 +0000 (10:10 +0200)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 22 Jul 2026 08:10:24 +0000 (10:10 +0200)
  improperly accounted client-terminated DNS-over-QUIC queries. Thanks
  to Qifan Zhang, Palo Alto Networks, for the report.

services/listen_dnsport.c
services/listen_dnsport.h
services/mesh.c
services/mesh.h
testcode/fake_event.c
util/netevent.c
util/netevent.h

index caae7fcc4b09fc710e1d6c192520fcdf5931fc87..0500bff8d1bb220387a174b4a930bad068c34766 100644 (file)
@@ -2166,7 +2166,8 @@ void tcp_req_info_clear(struct tcp_req_info* req)
        open = req->open_req_list;
        while(open) {
                nopen = open->next;
-               mesh_state_remove_reply(open->mesh, open->mesh_state, req->cp);
+               mesh_state_remove_reply(open->mesh, open->mesh_state, req->cp,
+                       NULL);
                free(open);
                open = nopen;
        }
@@ -3596,15 +3597,29 @@ doq_conn_create(struct comm_point* c, struct doq_pkt_addr* paddr,
        return conn;
 }
 
+/** The arguments for doq stream tree del. */
+struct doq_stream_tree_del_args {
+       /** The doq table. */
+       struct doq_table* table;
+       /** The doq connection for the stream. */
+       struct doq_conn* conn;
+};
+
 /** delete stream tree node */
 static void
 stream_tree_del(rbnode_type* node, void* arg)
 {
-       struct doq_table* table = (struct doq_table*)arg;
+       struct doq_stream_tree_del_args* args = (struct doq_stream_tree_del_args*)arg;
+       struct doq_table* table = args->table;
        struct doq_stream* stream;
        if(!node)
                return;
        stream = (struct doq_stream*)node;
+       if(stream->mesh_state) {
+               mesh_state_remove_reply(stream->mesh, stream->mesh_state,
+                       args->conn->doq_socket->cp, stream);
+               stream->mesh_state = NULL;
+       }
        if(stream->in)
                doq_table_quic_size_subtract(table, stream->inlen);
        if(stream->out)
@@ -3626,7 +3641,11 @@ doq_conn_delete(struct doq_conn* conn, struct doq_table* table)
         * because the ngtcp2 conn is deleted. */
        SSL_set_app_data(conn->ssl, NULL);
        if(conn->stream_tree.count != 0) {
-               traverse_postorder(&conn->stream_tree, stream_tree_del, table);
+               struct doq_stream_tree_del_args args;
+               memset(&args, 0, sizeof(args));
+               args.table = table;
+               args.conn = conn;
+               traverse_postorder(&conn->stream_tree, stream_tree_del, &args);
        }
        free(conn->key.dcid);
        SSL_free(conn->ssl);
@@ -3935,6 +3954,11 @@ doq_stream_close(struct doq_conn* conn, struct doq_stream* stream,
        if(stream->is_closed)
                return 1;
        stream->is_closed = 1;
+       if(stream->mesh_state) {
+               mesh_state_remove_reply(stream->mesh, stream->mesh_state,
+                       conn->doq_socket->cp, stream);
+               stream->mesh_state = NULL;
+       }
        doq_stream_off_write_list(conn, stream);
        if(send_shutdown) {
                verbose(VERB_ALGO, "doq: shutdown stream_id %d with app_error_code %d",
@@ -4013,7 +4037,33 @@ doq_stream_send_reply(struct doq_conn* conn, struct doq_stream* stream,
        doq_conn_write_enable(conn);
        return 1;
 }
+#endif /* HAVE_NGTCP2 */
 
+void
+doq_stream_add_meshstate(struct doq_stream* stream,
+       struct mesh_area* mesh, struct mesh_state* m)
+{
+#ifdef HAVE_NGTCP2
+       stream->mesh = mesh;
+       stream->mesh_state = m;
+#else
+       (void)stream; (void)mesh; (void)m;
+#endif
+}
+
+void
+doq_stream_remove_mesh_state(struct doq_stream* stream)
+{
+#ifdef HAVE_NGTCP2
+       if(!stream)
+               return;
+       stream->mesh_state = NULL;
+#else
+       (void)stream;
+#endif
+}
+
+#ifdef HAVE_NGTCP2
 /** doq stream data length has completed, allocations can be done. False on
  * allocation failure. */
 static int
@@ -4074,6 +4124,7 @@ doq_stream_data_complete(struct doq_conn* conn, struct doq_stream* stream)
                return 0;
        }
        c->repinfo.doq_streamid = stream->stream_id;
+       c->repinfo.doq_stream = stream;
        conn->doq_socket->current_conn = conn;
        fptr_ok(fptr_whitelist_comm_point(c->callback));
        if( (*c->callback)(c, c->cb_arg, NETEVENT_NOERROR, &c->repinfo)) {
index 95aa3e11ebe3be95d1f27903fa6fdaef4bc630e9..ae0463468520782f5daf4d3f0b2bb28a8c45a0d2 100644 (file)
@@ -61,6 +61,8 @@ struct config_file;
 struct addrinfo;
 struct sldns_buffer;
 struct tcl_list;
+struct mesh_area;
+struct mesh_state;
 
 /**
  * Listening for queries structure.
@@ -692,6 +694,11 @@ struct doq_stream {
        uint8_t* out;
        /** if the stream is on the write list */
        uint8_t on_write_list;
+       /** The mesh area and mesh state, set when this stream's query was
+        * dispatched into the mesh; used to detach the reply on stream close */
+       struct mesh_area* mesh;
+       /** the mesh state for the query, is nonNULL when there is one. */
+       struct mesh_state* mesh_state;
        /** the prev and next on the write list, if on the list */
        struct doq_stream* write_prev, *write_next;
 };
@@ -794,7 +801,16 @@ int doq_stream_close(struct doq_conn* conn, struct doq_stream* stream,
 /** send reply for a connection */
 int doq_stream_send_reply(struct doq_conn* conn, struct doq_stream* stream,
        struct sldns_buffer* buf);
+#endif /* HAVE_NGTCP2 */
 
+/** add mesh state to doq stream */
+void doq_stream_add_meshstate(struct doq_stream* stream,
+       struct mesh_area* mesh, struct mesh_state* m);
+
+/** remove mesh state from doq stream */
+void doq_stream_remove_mesh_state(struct doq_stream* stream);
+
+#ifdef HAVE_NGTCP2
 /** the connection has write interest, wants to write packets */
 void doq_conn_write_enable(struct doq_conn* conn);
 
index 2869010476985f469c8f7aa6dca393822539539f..f06c1cb9d6181780cce7849fba4c4af181094abb 100644 (file)
@@ -467,6 +467,8 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo,
                                "incoming query.");
                        if(rep->c->use_h2)
                                http2_stream_remove_mesh_state(rep->c->h2_stream);
+                       else if(rep->c->type == comm_doq && rep->doq_stream)
+                               doq_stream_remove_mesh_state(rep->doq_stream);
                        comm_point_drop_reply(rep);
                        mesh->stats_dropped++;
                        return;
@@ -480,6 +482,8 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo,
                                "dropping incoming query.");
                        if(rep->c->use_h2)
                                http2_stream_remove_mesh_state(rep->c->h2_stream);
+                       else if(rep->c->type == comm_doq && rep->doq_stream)
+                               doq_stream_remove_mesh_state(rep->doq_stream);
                        comm_point_drop_reply(rep);
                        mesh->num_queries_replyaddr_limit++;
                        return;
@@ -552,6 +556,8 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo,
        }
        if(rep->c->use_h2) {
                http2_stream_add_meshstate(rep->c->h2_stream, mesh, s);
+       } else if(rep->c->type == comm_doq && rep->doq_stream) {
+               doq_stream_add_meshstate(rep->doq_stream, mesh, s);
        }
        /* add serve expired timer if required and not already there */
        if(timeout && !mesh_serve_expired_init(s, timeout)) {
@@ -605,6 +611,8 @@ servfail_mem:
                qinfo, qid, qflags, edns);
        if(rep->c->use_h2)
                http2_stream_remove_mesh_state(rep->c->h2_stream);
+       else if(rep->c->type == comm_doq && rep->doq_stream)
+               doq_stream_remove_mesh_state(rep->doq_stream);
        comm_point_send_reply(rep);
        if(added)
                mesh_state_delete(&s->s);
@@ -1484,6 +1492,10 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep,
                 * for HTTP/2 stream to refer to mesh state, in case
                 * connection gets cleanup before HTTP/2 stream close. */
                r->h2_stream->mesh_state = NULL;
+#ifdef HAVE_NGTCP2
+       } else if(r->query_reply.doq_stream) {
+               r->query_reply.doq_stream->mesh_state = NULL;
+#endif
        }
        /* send the reply */
        /* We don't reuse the encoded answer if:
@@ -1777,6 +1789,8 @@ void mesh_query_done(struct mesh_state* mstate)
                                mstate->reply_list = NULL;
                                if(r->query_reply.c->use_h2)
                                        http2_stream_remove_mesh_state(r->h2_stream);
+                               else if(r->query_reply.doq_stream)
+                                       doq_stream_remove_mesh_state(r->query_reply.doq_stream);
                                comm_point_drop_reply(&r->query_reply);
                                mstate->reply_list = reply_list;
                                log_assert(mstate->s.env->mesh->num_reply_addrs > 0);
@@ -1814,6 +1828,8 @@ void mesh_query_done(struct mesh_state* mstate)
                        mstate->reply_list = NULL;
                        if(r->query_reply.c->use_h2) {
                                http2_stream_remove_mesh_state(r->h2_stream);
+                       } else if(r->query_reply.doq_stream) {
+                               doq_stream_remove_mesh_state(r->query_reply.doq_stream);
                        }
                        comm_point_drop_reply(&r->query_reply);
                        mstate->reply_list = reply_list;
@@ -2009,6 +2025,8 @@ int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns,
        if(rep->c->use_h2)
                r->h2_stream = rep->c->h2_stream;
        else    r->h2_stream = NULL;
+       if(rep->c->type != comm_doq)
+               r->query_reply.doq_stream = NULL;
 
        /* Data related to local alias stored in 'qinfo' (if any) is ephemeral
         * and can be different for different original queries (even if the
@@ -2366,7 +2384,7 @@ void mesh_list_remove(struct mesh_state* m, struct mesh_state** fp,
 }
 
 void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m,
-       struct comm_point* cp)
+       struct comm_point* cp, struct doq_stream* doq_stream)
 {
        struct mesh_reply* n, *prev = NULL;
        n = m->reply_list;
@@ -2374,7 +2392,8 @@ void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m,
         * there is no accounting twice */
        if(!n) return; /* nothing to remove, also no accounting needed */
        while(n) {
-               if(n->query_reply.c == cp) {
+               if(n->query_reply.c == cp
+                       && (!doq_stream || n->query_reply.doq_stream == doq_stream)) {
                        /* unlink it */
                        if(prev) prev->next = n->next;
                        else m->reply_list = n->next;
@@ -2387,6 +2406,10 @@ void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m,
                         * share the same comm_point); make sure the streams
                         * don't point back. */
                        if(n->h2_stream) n->h2_stream->mesh_state = NULL;
+#ifdef HAVE_NGTCP2
+                       if(n->query_reply.doq_stream)
+                               n->query_reply.doq_stream->mesh_state = NULL;
+#endif
 
                        /* prev = prev; */
                        n = n->next;
@@ -2554,6 +2577,8 @@ mesh_serve_expired_callback(void* arg)
                        mstate->reply_list = NULL;
                        if(r->query_reply.c->use_h2)
                                http2_stream_remove_mesh_state(r->h2_stream);
+                       else if(r->query_reply.doq_stream)
+                               doq_stream_remove_mesh_state(r->query_reply.doq_stream);
                        comm_point_drop_reply(&r->query_reply);
                        mstate->reply_list = reply_list;
                        mstate->s.env->mesh->num_queries_discard_timeout++;
index 9ee585156fd4397c038eea544e7bbf7590b196ed..352260e26eeace9c751b3fad5233ea9077363dcd 100644 (file)
@@ -683,9 +683,11 @@ void mesh_list_remove(struct mesh_state* m, struct mesh_state** fp,
  * @param mesh: to update the counters.
  * @param m: the mesh state.
  * @param cp: the comm_point to remove from the list.
+ * @param doq_stream: if not NULL, it specifies the doq_stream to match
+ *     for the delete.
  */
 void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m,
-       struct comm_point* cp);
+       struct comm_point* cp, struct doq_stream* doq_stream);
 
 /** Callback for when the serve expired client timer has run out.  Tries to
  * find an expired answer in the cache and reply that to the client.
index ce439edd1294a1fb998a1aefbcfff1ffb471db75..b127d0df80921da0a397c835620741f791bdce3a 100644 (file)
@@ -2021,6 +2021,15 @@ void http2_stream_remove_mesh_state(struct http2_stream* ATTR_UNUSED(h2_stream))
 {
 }
 
+void doq_stream_add_meshstate(struct doq_stream* ATTR_UNUSED(stream),
+       struct mesh_area* ATTR_UNUSED(mesh), struct mesh_state* ATTR_UNUSED(m))
+{
+}
+
+void doq_stream_remove_mesh_state(struct doq_stream* ATTR_UNUSED(stream))
+{
+}
+
 void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(event),
        void* ATTR_UNUSED(arg))
 {
index 58938a2201159a720e0481514cba06e29e508264..b7092b88a876d02ddb06f07710d00b3dee8b0183 100644 (file)
@@ -3166,7 +3166,7 @@ static void http2_stream_delete(struct http2_session* h2_session,
 {
        if(h2_stream->mesh_state) {
                mesh_state_remove_reply(h2_stream->mesh, h2_stream->mesh_state,
-                       h2_session->c);
+                       h2_session->c, NULL);
                h2_stream->mesh_state = NULL;
        }
        http2_req_stream_clear(h2_stream);
index 7235843ee10850a59b5636ec2ed72e87d87e543b..aee8a653988c5b21f6a57f83c8797d011a28da8e 100644 (file)
@@ -187,6 +187,8 @@ struct comm_reply {
        /** port number for doq */
        int doq_srcport;
 #endif /* HAVE_NGTCP2 */
+       /** The doq stream to register mesh states to. */
+       struct doq_stream* doq_stream;
 };
 
 /**