]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
replace QCTX_INIT/_DESTROY hooks with QUERY_SETUP/_RESET
authorColin Vidal <colin@isc.org>
Mon, 8 Sep 2025 08:10:37 +0000 (10:10 +0200)
committerColin Vidal <colin@isc.org>
Tue, 9 Sep 2025 07:42:34 +0000 (09:42 +0200)
The hook NS_QUERY_QCTX_DESTROY is problematic with zone plugins because
it can be called in some contexts where `qctx->client` is invalid (the
pointer is dangling); which would lead to a use-after-free (spotted by
TSAN build) as `qctx->client` is used to get the zone hooktable, to find
out whether there is an authoritive zone which would have
NS_QUERY_QCTX_DESTROY registered.

This can't easily be fixed, because there is no easy way to know from
query.c code if `client` is still a valid object: `client->reqhandle`,
representing the request from a client, is refcounted, and the `client`
object is freed from memory once its refcounter gets to 0. While
`reqhandle` is attached from query.c code, it can be attached more than
once from asynchronous code and there is no clear path where detaching
it would lead to a client free. Hence, there is no way to know for sure
when to set `qctx->client = NULL` (this is why the pointer remains
dangling).

Back to the original problem; this is why the NS_QUERY_QCTX_DESTROY hook
is incompatible with zone plugins. `qctx->detach_client`, which is used
to tell a plugin that the `client` object is either free or about to be
free can't be use either, because in some cases the client is still
there, and should be used.

Code issue aside, the `qctx` object is really just an aggregate of
various data to pass easily in the various functions and callbacks,
initially stored on the stack, but allocated in some cases (for some
asynchronous flow, when recursion is needed), so the point it gets
created/"destroyed" is really just an implementation "detail", and
providing a higher level hook for the plugin would be beneficial. Hence,
NS_QUERY_RESET and NS_QUERY_INIT are removed, and instead, the existing
NS_QUERY_SETUP can be used as well as the newly introduced
NS_QUERY_RESET (which replaces NS_QUERY_QCTX_DESTROY). The advanage is
that NS_QUERY_RESET is called _only_ when the client object is _always_
about to be freed, which avoids usage of the extra `qctx->detach_client`
usage from the plugin.

The way NS_QUERY_RESET works is that when the `client` is freed, a
callback (from `query.c`) is called. This callback creates a transient
qctx object on the stack with a pointer to the view, and uses that
to call the hook.

lib/ns/include/ns/hooks.h
lib/ns/query.c

index 363e1ac3a5270e7d1e59ae172d4293718f154d09..3b1b4b605737770b2c74f3dbc49cdb247678f4c9 100644 (file)
  */
 typedef enum {
        /* hookpoints from query.c */
-       NS_QUERY_QCTX_INITIALIZED,
-       NS_QUERY_QCTX_DESTROYED,
        NS_QUERY_SETUP,
+       NS_QUERY_RESET,
        NS_QUERY_START_BEGIN,
        NS_QUERY_LOOKUP_BEGIN,
        NS_QUERY_RESUME_BEGIN,
index 123a0a0a32739b8b77509a08afb6e3e34941458e..77419b77c7e72c36884cb00b7830038d6b4f6c8e 100644 (file)
@@ -822,6 +822,10 @@ ns_query_cancel(ns_client_t *client) {
 
 static void
 query_reset(ns_client_t *client, bool everything) {
+       query_ctx_t qctx = { .view = client->inner.view, .client = client };
+
+       CALL_HOOK_NORETURN(NS_QUERY_RESET, &qctx);
+
        CTRACE(ISC_LOG_DEBUG(3), "query_reset");
 
        /*%
@@ -5132,8 +5136,6 @@ qctx_init(ns_client_t *client, dns_fetchresponse_t **frespp,
        if (dns_rdatatype_issig(qctx->qtype)) {
                qctx->type = dns_rdatatype_any;
        }
-
-       CALL_HOOK_NORETURN(NS_QUERY_QCTX_INITIALIZED, qctx);
 }
 
 /*%