]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
refactor fctx_done() to set fctx to NULL
authorEvan Hunt <each@isc.org>
Wed, 20 Apr 2022 22:05:12 +0000 (15:05 -0700)
committerEvan Hunt <each@isc.org>
Wed, 27 Apr 2022 17:54:28 +0000 (10:54 -0700)
previously fctx_done() detached the fctx but did not clear the pointer
passed into it from the caller.  in some conditions, when rctx_done()
was reached while waiting for a validator to complete, fctx_done()
could be called twice on the same fetch, causing a double detach.

fctx_done() now clears the fctx pointer, to reduce the chances of
such mistakes.

lib/dns/resolver.c

index cd647fea82f21eb50757edf6d826a9d8ec5d2821..b26b15ae61fded71365e085b7ec0cb2fae8dda64 100644 (file)
        } while (0)
 #endif /* WANT_QUERYTRACE */
 
+/*
+ * Add or remove an extra fctx reference without setting or clearing
+ * the pointer.
+ */
+#define fctx_addref(f) fctx_attach((f), &(fetchctx_t *){ NULL })
+#define fctx_unref(f)  fctx_detach(&(fetchctx_t *){ (f) })
+
 #define US_PER_SEC  1000000U
 #define US_PER_MSEC 1000U
 #define NS_PER_US   1000U
@@ -280,7 +287,7 @@ typedef enum {
        fetchstate_init = 0, /*%< Start event has not run yet. */
        fetchstate_active,
        fetchstate_done /*%< FETCHDONE events posted. */
-} fetchstate;
+} fetchstate_t;
 
 typedef enum {
        badns_unreachable = 0,
@@ -308,7 +315,7 @@ struct fetchctx {
        isc_refcount_t references;
 
        /*% Locked by appropriate bucket lock. */
-       fetchstate state;
+       fetchstate_t state;
        atomic_bool want_shutdown;
        bool cloned;
        bool spilled;
@@ -657,6 +664,8 @@ findnoqname(fetchctx_t *fctx, dns_message_t *message, dns_name_t *name,
 #define fctx_attach(fctx, fctxp) \
        fctx__attach(fctx, fctxp, __FILE__, __LINE__, __func__)
 #define fctx_detach(fctxp) fctx__detach(fctxp, __FILE__, __LINE__, __func__)
+#define fctx_done_detach(fctxp, result) \
+       fctx__done_detach(fctxp, result, __FILE__, __LINE__, __func__);
 
 static void
 fctx__attach(fetchctx_t *fctx, fetchctx_t **fctxp, const char *file,
@@ -665,6 +674,10 @@ static void
 fctx__detach(fetchctx_t **fctxp, const char *file, unsigned int line,
             const char *func);
 
+static void
+fctx__done_detach(fetchctx_t **fctxp, isc_result_t result, const char *file,
+                 unsigned int line, const char *func);
+
 static void
 resume_qmin(isc_task_t *task, isc_event_t *event);
 
@@ -1467,9 +1480,11 @@ fctx_cancelquery(resquery_t **queryp, isc_time_t *finish, bool no_response,
                dns_dispatch_cancel(&query->dispentry);
        }
 
+       LOCK(&fctx->res->buckets[fctx->bucketnum].lock);
        if (ISC_LINK_LINKED(query, link)) {
                ISC_LIST_UNLINK(fctx->queries, query, link);
        }
+       UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock);
 
        resquery_detach(queryp);
 }
@@ -1486,7 +1501,7 @@ fctx_cleanup(fetchctx_t *fctx) {
                next_find = ISC_LIST_NEXT(find, publink);
                ISC_LIST_UNLINK(fctx->finds, find, publink);
                dns_adb_destroyfind(&find);
-               fctx_detach(&(fetchctx_t *){ fctx });
+               fctx_unref(fctx);
        }
        fctx->find = NULL;
 
@@ -1495,7 +1510,7 @@ fctx_cleanup(fetchctx_t *fctx) {
                next_find = ISC_LIST_NEXT(find, publink);
                ISC_LIST_UNLINK(fctx->altfinds, find, publink);
                dns_adb_destroyfind(&find);
-               fctx_detach(&(fetchctx_t *){ fctx });
+               fctx_unref(fctx);
        }
        fctx->altfind = NULL;
 
@@ -1517,12 +1532,30 @@ fctx_cleanup(fetchctx_t *fctx) {
 static void
 fctx_cancelqueries(fetchctx_t *fctx, bool no_response, bool age_untried) {
        resquery_t *query = NULL, *next_query = NULL;
+       ISC_LIST(resquery_t) queries;
 
        FCTXTRACE("cancelqueries");
 
-       for (query = ISC_LIST_HEAD(fctx->queries); query != NULL;
-            query = next_query) {
+       ISC_LIST_INIT(queries);
+
+       /*
+        * Move the queries to a local list so we can cancel
+        * them without holding the lock.
+        */
+       LOCK(&fctx->res->buckets[fctx->bucketnum].lock);
+       ISC_LIST_MOVE(queries, fctx->queries);
+       UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock);
+
+       for (query = ISC_LIST_HEAD(queries); query != NULL; query = next_query)
+       {
                next_query = ISC_LIST_NEXT(query, link);
+
+               /*
+                * Note that we have to unlink the query here,
+                * because if it's still linked in fctx_cancelquery(),
+                * then it will try to unlink it from fctx->queries.
+                */
+               ISC_LIST_UNLINK(queries, query, link);
                fctx_cancelquery(&query, NULL, no_response, age_untried);
        }
 }
@@ -1746,16 +1779,33 @@ fctx_sendevents(fetchctx_t *fctx, isc_result_t result, int line) {
 }
 
 static void
-fctx_done(fetchctx_t *fctx, isc_result_t result, int line) {
-       dns_resolver_t *res;
+fctx__done_detach(fetchctx_t **fctxp, isc_result_t result, const char *file,
+                 unsigned int line, const char *func) {
+       fetchctx_t *fctx = NULL;
+       dns_resolver_t *res = NULL;
        bool no_response = false;
        bool age_untried = false;
 
-       REQUIRE(line >= 0);
+       REQUIRE(fctxp != NULL && VALID_FCTX(*fctxp));
+
+       fctx = *fctxp;
+       res = fctx->res;
 
        FCTXTRACE("done");
 
-       res = fctx->res;
+#ifdef FCTX_TRACE
+       fprintf(stderr, "%s:%s:%u:%s(%p, %p): %s\n", func, file, line, __func__,
+               fctx, fctxp, isc_result_totext(result));
+#else
+       UNUSED(file);
+       UNUSED(line);
+       UNUSED(func);
+#endif
+
+       LOCK(&res->buckets[fctx->bucketnum].lock);
+       INSIST(fctx->state != fetchstate_done);
+       fctx->state = fetchstate_done;
+       UNLOCK(&res->buckets[fctx->bucketnum].lock);
 
        if (result == ISC_R_SUCCESS) {
                if (fctx->qmin_warning != ISC_R_SUCCESS) {
@@ -1788,13 +1838,12 @@ fctx_done(fetchctx_t *fctx, isc_result_t result, int line) {
        fctx_stoptimer(fctx);
 
        LOCK(&res->buckets[fctx->bucketnum].lock);
-       fctx->state = fetchstate_done;
        FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
        fctx_sendevents(fctx, result, line);
        fctx_shutdown(fctx);
        UNLOCK(&res->buckets[fctx->bucketnum].lock);
 
-       fctx_detach(&fctx);
+       fctx_detach(fctxp);
 }
 
 static void
@@ -1844,7 +1893,7 @@ resquery_senddone(isc_result_t eresult, isc_region_t *region, void *arg) {
                           "due to unexpected result; responding",
                           eresult);
                fctx_cancelquery(&copy, NULL, false, false);
-               fctx_done(fctx, eresult, __LINE__);
+               fctx_done_detach(&fctx, eresult);
                break;
        }
 
@@ -2872,7 +2921,7 @@ resquery_connected(isc_result_t eresult, isc_region_t *region, void *arg) {
                                  "responding");
 
                        fctx_cancelquery(&copy, NULL, false, false);
-                       fctx_done(fctx, result, __LINE__);
+                       fctx_done_detach(&fctx, result);
                        break;
                }
 
@@ -2894,7 +2943,7 @@ resquery_connected(isc_result_t eresult, isc_region_t *region, void *arg) {
        case ISC_R_SHUTTINGDOWN:
                FCTXTRACE3("shutdown in resquery_connected()", eresult);
                fctx_cancelquery(&copy, NULL, true, false);
-               fctx_done(fctx, eresult, __LINE__);
+               fctx_done_detach(&fctx, eresult);
                break;
 
        case ISC_R_NETUNREACH:
@@ -2924,7 +2973,7 @@ resquery_connected(isc_result_t eresult, isc_region_t *region, void *arg) {
                           eresult);
 
                fctx_cancelquery(&copy, NULL, false, false);
-               fctx_done(fctx, eresult, __LINE__);
+               fctx_done_detach(&fctx, eresult);
                break;
        }
 
@@ -2980,15 +3029,19 @@ fctx_finddone(isc_task_t *task, isc_event_t *event) {
 
        dns_adb_destroyfind(&find);
 
-       if (want_try) {
-               fctx_try(fctx, true, false);
-       } else if (want_done) {
+       if (want_done) {
                FCTXTRACE("fetch failed in finddone(); return "
                          "ISC_R_FAILURE");
-               fctx_done(fctx, ISC_R_FAILURE, __LINE__);
-       }
 
-       fctx_detach(&fctx);
+               /* Detach the extra reference from findname(). */
+               fctx_unref(fctx);
+               fctx_done_detach(&fctx, ISC_R_FAILURE);
+       } else if (want_try) {
+               fctx_try(fctx, true, false);
+               fctx_detach(&fctx);
+       } else {
+               fctx_detach(&fctx);
+       }
 }
 
 static bool
@@ -3311,7 +3364,7 @@ findname(fetchctx_t *fctx, const dns_name_t *name, in_port_t port,
        /*
         * See what we know about this address.
         */
-       fctx_attach(fctx, &(fetchctx_t *){ NULL });
+       fctx_addref(fctx);
        result = dns_adb_createfind(
                fctx->adb, res->buckets[fctx->bucketnum].task, fctx_finddone,
                fctx, name, fctx->name, fctx->type, options, now, NULL,
@@ -4006,7 +4059,6 @@ fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) {
        dns_resolver_t *res;
        isc_task_t *task;
        unsigned int bucketnum;
-       fetchctx_t *ev_fctx = NULL;
 
        FCTXTRACE5("try", "fctx->qc=", isc_counter_used(fctx->qc));
 
@@ -4023,7 +4075,7 @@ fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) {
                              "(querycount=%u, maxqueries=%u)",
                              fctx->info, isc_counter_used(fctx->qc),
                              res->maxqueries);
-               fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+               fctx_done_detach(&fctx, DNS_R_SERVFAIL);
                return;
        }
 
@@ -4050,7 +4102,7 @@ fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) {
                        /*
                         * Something bad happened.
                         */
-                       fctx_done(fctx, result, __LINE__);
+                       fctx_done_detach(&fctx, result);
                        return;
                }
 
@@ -4066,7 +4118,7 @@ fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) {
                 * might be bad ones.  In this case, return SERVFAIL.
                 */
                if (addrinfo == NULL) {
-                       fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+                       fctx_done_detach(&fctx, DNS_R_SERVFAIL);
                        return;
                }
        }
@@ -4107,7 +4159,7 @@ fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) {
                                      fctx->qminfetch,
                                      validfctx ? fctx->qminfetch->private->info
                                                : "<invalid>");
-                       fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+                       fctx_done_detach(&fctx, DNS_R_SERVFAIL);
                        return;
                }
 
@@ -4119,16 +4171,16 @@ fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) {
                if ((options & DNS_FETCHOPT_QMIN_USE_A) != 0) {
                        options |= DNS_FETCHOPT_NOFOLLOW;
                }
-               fctx_attach(fctx, &ev_fctx);
+               fctx_addref(fctx);
                task = res->buckets[bucketnum].task;
                result = dns_resolver_createfetch(
                        fctx->res, fctx->qminname, fctx->qmintype, fctx->domain,
                        &fctx->nameservers, NULL, NULL, 0, options, 0, fctx->qc,
-                       task, resume_qmin, ev_fctx, &fctx->qminrrset, NULL,
+                       task, resume_qmin, fctx, &fctx->qminrrset, NULL,
                        &fctx->qminfetch);
                if (result != ISC_R_SUCCESS) {
-                       fctx_detach(&ev_fctx);
-                       fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+                       fctx_unref(fctx);
+                       fctx_done_detach(&fctx, DNS_R_SERVFAIL);
                }
                return;
        }
@@ -4139,13 +4191,13 @@ fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) {
                              DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
                              "exceeded max queries resolving '%s'",
                              fctx->info);
-               fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+               fctx_done_detach(&fctx, DNS_R_SERVFAIL);
                return;
        }
 
        result = fctx_query(fctx, addrinfo, fctx->options);
        if (result != ISC_R_SUCCESS) {
-               fctx_done(fctx, result, __LINE__);
+               fctx_done_detach(&fctx, result);
        } else if (retrying) {
                inc_stats(res, dns_resstatscounter_retry);
        }
@@ -4153,16 +4205,16 @@ fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) {
 
 static void
 resume_qmin(isc_task_t *task, isc_event_t *event) {
-       dns_fetchevent_t *fevent;
-       dns_resolver_t *res;
-       fetchctx_t *fctx;
+       dns_fetchevent_t *fevent = NULL;
+       dns_resolver_t *res = NULL;
+       fetchctx_t *fctx = NULL;
        isc_result_t result;
        unsigned int bucketnum;
        unsigned int findoptions = 0;
-       dns_name_t *fname, *dcname;
+       dns_name_t *fname = NULL, *dcname = NULL;
        dns_fixedname_t ffixed, dcfixed;
-       fname = dns_fixedname_initname(&ffixed);
-       dcname = dns_fixedname_initname(&dcfixed);
+
+       UNUSED(task);
 
        REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
        fevent = (dns_fetchevent_t *)event;
@@ -4170,9 +4222,11 @@ resume_qmin(isc_task_t *task, isc_event_t *event) {
        REQUIRE(VALID_FCTX(fctx));
        res = fctx->res;
 
-       UNUSED(task);
        FCTXTRACE("resume_qmin");
 
+       fname = dns_fixedname_initname(&ffixed);
+       dcname = dns_fixedname_initname(&dcfixed);
+
        if (fevent->node != NULL) {
                dns_db_detachnode(fevent->db, &fevent->node);
        }
@@ -4185,8 +4239,8 @@ resume_qmin(isc_task_t *task, isc_event_t *event) {
        if (dns_rdataset_isassociated(fevent->rdataset)) {
                dns_rdataset_disassociate(fevent->rdataset);
        }
+
        result = fevent->result;
-       fevent = NULL;
        isc_event_free(&event);
 
        dns_resolver_destroyfetch(&fctx->qminfetch);
@@ -4195,19 +4249,12 @@ resume_qmin(isc_task_t *task, isc_event_t *event) {
        if (SHUTTINGDOWN(fctx)) {
                maybe_destroy(fctx, true);
                UNLOCK(&res->buckets[bucketnum].lock);
-               goto cleanup;
+               fctx_detach(&fctx);
+               return;
        }
        UNLOCK(&res->buckets[bucketnum].lock);
 
-       /*
-        * Note: fevent->rdataset must be disassociated and
-        * isc_event_free(&event) be called before resuming
-        * processing of the 'fctx' to prevent use-after-free.
-        * 'fevent' is set to NULL so as to not have a dangling
-        * pointer.
-        */
        if (result == ISC_R_CANCELED) {
-               fctx_done(fctx, result, __LINE__);
                goto cleanup;
        }
 
@@ -4218,7 +4265,6 @@ resume_qmin(isc_task_t *task, isc_event_t *event) {
         * Otherwise - either disable minimization if we're
         * in relaxed mode or fail if we're in strict mode.
         */
-
        if ((NXDOMAIN_RESULT(result) &&
             (fctx->options & DNS_FETCHOPT_QMIN_USE_A) == 0) ||
            result == DNS_R_FORMERR || result == DNS_R_REMOTEFORMERR ||
@@ -4233,7 +4279,6 @@ resume_qmin(isc_task_t *task, isc_event_t *event) {
                         */
                        fctx->qmin_warning = result;
                } else {
-                       fctx_done(fctx, result, __LINE__);
                        goto cleanup;
                }
        }
@@ -4259,7 +4304,6 @@ resume_qmin(isc_task_t *task, isc_event_t *event) {
        }
 
        if (result != ISC_R_SUCCESS) {
-               fctx_done(fctx, result, __LINE__);
                goto cleanup;
        }
        fcount_decr(fctx);
@@ -4268,7 +4312,6 @@ resume_qmin(isc_task_t *task, isc_event_t *event) {
 
        result = fcount_incr(fctx, false);
        if (result != ISC_R_SUCCESS) {
-               fctx_done(fctx, result, __LINE__);
                goto cleanup;
        }
 
@@ -4278,7 +4321,6 @@ resume_qmin(isc_task_t *task, isc_event_t *event) {
 
        result = fctx_minimize_qname(fctx);
        if (result != ISC_R_SUCCESS) {
-               fctx_done(fctx, result, __LINE__);
                goto cleanup;
        }
 
@@ -4294,12 +4336,13 @@ resume_qmin(isc_task_t *task, isc_event_t *event) {
        }
 
        fctx_try(fctx, true, false);
+       fctx_detach(&fctx);
+       return;
 
 cleanup:
-       INSIST(event == NULL);
-       INSIST(fevent == NULL);
-
-       fctx_detach(&fctx);
+       /* Detach the extra reference from fctx_try() */
+       fctx_unref(fctx);
+       fctx_done_detach(&fctx, result);
 }
 
 static void
@@ -4311,8 +4354,6 @@ fctx_destroy(fetchctx_t *fctx, bool exiting) {
        bool bucket_empty = false;
 
        REQUIRE(VALID_FCTX(fctx));
-       REQUIRE(fctx->state == fetchstate_done ||
-               fctx->state == fetchstate_init);
        REQUIRE(ISC_LIST_EMPTY(fctx->events));
        REQUIRE(ISC_LIST_EMPTY(fctx->queries));
        REQUIRE(ISC_LIST_EMPTY(fctx->finds));
@@ -4326,6 +4367,8 @@ fctx_destroy(fetchctx_t *fctx, bool exiting) {
        bucketnum = fctx->bucketnum;
 
        LOCK(&res->buckets[bucketnum].lock);
+       REQUIRE(fctx->state != fetchstate_active);
+
        ISC_LIST_UNLINK(res->buckets[bucketnum].fctxs, fctx, link);
 
        INSIST(atomic_fetch_sub_release(&res->nfctx, 1) > 0);
@@ -4421,9 +4464,9 @@ fctx_shutdown(fetchctx_t *fctx) {
 static void
 fctx_doshutdown(isc_task_t *task, isc_event_t *event) {
        fetchctx_t *fctx = event->ev_arg;
-       dns_resolver_t *res;
+       dns_resolver_t *res = NULL;
        unsigned int bucketnum;
-       dns_validator_t *validator;
+       dns_validator_t *validator = NULL;
 
        REQUIRE(VALID_FCTX(fctx));
 
@@ -4471,14 +4514,16 @@ fctx_doshutdown(isc_task_t *task, isc_event_t *event) {
 
        FCTX_ATTR_SET(fctx, FCTX_ATTR_SHUTTINGDOWN);
 
-       INSIST(fctx->state == fetchstate_active ||
-              fctx->state == fetchstate_done);
+       INSIST(fctx->state != fetchstate_init);
        INSIST(atomic_load_acquire(&fctx->want_shutdown));
 
-       if (fctx->state != fetchstate_done) {
+       if (fctx->state == fetchstate_active) {
                fctx->state = fetchstate_done;
+
                fctx_sendevents(fctx, ISC_R_CANCELED, __LINE__);
-               fctx_detach(&(fetchctx_t *){ fctx });
+
+               /* Detach the extra ref from dns_resolver_createfetch(). */
+               fctx_unref(fctx);
        }
 
        UNLOCK(&res->buckets[bucketnum].lock);
@@ -4518,8 +4563,10 @@ fctx_start(isc_task_t *task, isc_event_t *event) {
                UNLOCK(&res->buckets[bucketnum].lock);
 
                FCTX_ATTR_SET(fctx, FCTX_ATTR_SHUTTINGDOWN);
-               fctx_done(fctx, ISC_R_SHUTTINGDOWN, __LINE__);
-               fctx_detach(&fctx);
+
+               /* Detach the extra ref from dns_resolver_createfetch(). */
+               fctx_unref(fctx);
+               fctx_done_detach(&fctx, ISC_R_SHUTTINGDOWN);
                return;
        }
 
@@ -4527,6 +4574,7 @@ fctx_start(isc_task_t *task, isc_event_t *event) {
         * Normal fctx startup.
         */
        fctx->state = fetchstate_active;
+
        /*
         * Reset the control event for later use in shutting
         * down the fctx.
@@ -4545,7 +4593,7 @@ fctx_start(isc_task_t *task, isc_event_t *event) {
         */
        result = fctx_starttimer(fctx);
        if (result != ISC_R_SUCCESS) {
-               fctx_done(fctx, result, __LINE__);
+               fctx_done_detach(&fctx, result);
        } else {
                fctx_try(fctx, false, false);
        }
@@ -4634,7 +4682,9 @@ fctx_expired(isc_task_t *task, isc_event_t *event) {
        isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
                      DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
                      "shut down hung fetch while resolving '%s'", fctx->info);
+       LOCK(&fctx->res->buckets[fctx->bucketnum].lock);
        fctx_shutdown(fctx);
+       UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock);
        isc_event_free(&event);
 }
 
@@ -5326,19 +5376,19 @@ has_000_label(dns_rdataset_t *nsecset) {
  */
 static void
 validated(isc_task_t *task, isc_event_t *event) {
-       dns_adbaddrinfo_t *addrinfo;
+       dns_adbaddrinfo_t *addrinfo = NULL;
        dns_dbnode_t *node = NULL;
        dns_dbnode_t *nsnode = NULL;
-       dns_fetchevent_t *hevent;
-       dns_name_t *name;
+       dns_fetchevent_t *hevent = NULL;
+       dns_name_t *name = NULL;
        dns_rdataset_t *ardataset = NULL;
        dns_rdataset_t *asigrdataset = NULL;
-       dns_rdataset_t *rdataset;
-       dns_rdataset_t *sigrdataset;
-       dns_resolver_t *res;
-       dns_valarg_t *valarg;
-       dns_validatorevent_t *vevent;
-       fetchctx_t *fctx = NULL;
+       dns_rdataset_t *rdataset = NULL;
+       dns_rdataset_t *sigrdataset = NULL;
+       dns_resolver_t *res = NULL;
+       dns_valarg_t *valarg = NULL;
+       dns_validatorevent_t *vevent = NULL;
+       fetchctx_t *fctx = NULL, *vfctx = NULL;
        bool chaining;
        bool negative;
        bool sentresponse;
@@ -5362,6 +5412,7 @@ validated(isc_task_t *task, isc_event_t *event) {
 
        fctx = valarg->fctx;
        valarg->fctx = NULL;
+       vfctx = fctx;
 
        FCTXTRACE("received validation completion event");
 
@@ -5512,7 +5563,7 @@ validated(isc_task_t *task, isc_event_t *event) {
                if (fctx->validator != NULL) {
                        dns_validator_send(fctx->validator);
                } else if (sentresponse) {
-                       fctx_done(fctx, result, __LINE__); /* Locks bucket */
+                       fctx_done_detach(&fctx, result); /* Locks bucket */
                } else if (result == DNS_R_BROKENCHAIN) {
                        isc_result_t tresult;
                        isc_time_t expire;
@@ -5528,13 +5579,13 @@ validated(isc_task_t *task, isc_event_t *event) {
                                dns_resolver_addbadcache(res, fctx->name,
                                                         fctx->type, &expire);
                        }
-                       fctx_done(fctx, result, __LINE__); /* Locks bucket */
+                       fctx_done_detach(&fctx, result); /* Locks bucket */
                } else {
                        fctx_try(fctx, true, true); /* Locks bucket */
                }
 
                dns_message_detach(&message);
-               fctx_detach(&fctx);
+               fctx_detach(&vfctx);
                return;
        }
 
@@ -5830,12 +5881,12 @@ noanswer_response:
        }
 
        UNLOCK(&res->buckets[bucketnum].lock);
-       fctx_done(fctx, result, __LINE__); /* Locks bucket. */
+       fctx_done_detach(&fctx, result); /* Locks bucket. */
 
 cleanup_event:
        INSIST(node == NULL);
        dns_message_detach(&message);
-       fctx_detach(&fctx);
+       fctx_detach(&vfctx);
        isc_event_free(&event);
 }
 
@@ -7229,16 +7280,17 @@ static void
 resume_dslookup(isc_task_t *task, isc_event_t *event) {
        dns_fetchevent_t *fevent = NULL;
        dns_resolver_t *res = NULL;
-       fetchctx_t *fctx = NULL;
+       fetchctx_t *fctx = NULL, *ds_fctx = NULL;
        isc_result_t result;
        dns_rdataset_t nameservers;
        dns_fixedname_t fixed;
        dns_name_t *domain = NULL;
-       fetchctx_t *ev_fctx = NULL;
 
        REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
+
        fevent = (dns_fetchevent_t *)event;
        fctx = event->ev_arg;
+       ds_fctx = fctx;
 
        REQUIRE(VALID_FCTX(fctx));
        res = fctx->res;
@@ -7259,15 +7311,11 @@ resume_dslookup(isc_task_t *task, isc_event_t *event) {
         * Note: fevent->rdataset must be disassociated and
         * isc_event_free(&event) be called before resuming
         * processing of the 'fctx' to prevent use-after-free.
-        * 'fevent' is set to NULL so as to not have a dangling
-        * pointer.
         */
        if (fevent->result == ISC_R_CANCELED) {
                if (dns_rdataset_isassociated(fevent->rdataset)) {
                        dns_rdataset_disassociate(fevent->rdataset);
                }
-               fevent = NULL;
-               isc_event_free(&event);
 
                dns_resolver_destroyfetch(&fctx->nsfetch);
 
@@ -7278,8 +7326,7 @@ resume_dslookup(isc_task_t *task, isc_event_t *event) {
                        goto cleanup;
                }
                UNLOCK(&res->buckets[fctx->bucketnum].lock);
-
-               fctx_done(fctx, ISC_R_CANCELED, __LINE__);
+               fctx_done_detach(&fctx, ISC_R_CANCELED);
        } else if (fevent->result == ISC_R_SUCCESS) {
                FCTXTRACE("resuming DS lookup");
 
@@ -7295,8 +7342,6 @@ resume_dslookup(isc_task_t *task, isc_event_t *event) {
                if (dns_rdataset_isassociated(fevent->rdataset)) {
                        dns_rdataset_disassociate(fevent->rdataset);
                }
-               fevent = NULL;
-               isc_event_free(&event);
 
                LOCK(&res->buckets[fctx->bucketnum].lock);
                if (SHUTTINGDOWN(fctx)) {
@@ -7309,21 +7354,21 @@ resume_dslookup(isc_task_t *task, isc_event_t *event) {
                fcount_decr(fctx);
                dns_name_copy(fctx->nsname, fctx->domain);
                result = fcount_incr(fctx, true);
-               if (result != ISC_R_SUCCESS) {
-                       fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
-                       goto cleanup;
+               if (result == ISC_R_SUCCESS) {
+                       /*
+                        * Try again.
+                        */
+                       fctx_try(fctx, true, false);
+               } else {
+                       fctx_done_detach(&fctx, DNS_R_SERVFAIL);
                }
-               /*
-                * Try again.
-                */
-               fctx_try(fctx, true, false);
        } else {
                unsigned int n;
                dns_rdataset_t *nsrdataset = NULL;
 
                /*
-                * Retrieve state from fctx->nsfetch before we destroy
-                * it.
+                * Get domain and nameservers from fctx->nsfetch
+                * before we destroy it.
                 */
                domain = dns_fixedname_initname(&fixed);
                dns_name_copy(fctx->nsfetch->private->domain, domain);
@@ -7331,8 +7376,6 @@ resume_dslookup(isc_task_t *task, isc_event_t *event) {
                        if (dns_rdataset_isassociated(fevent->rdataset)) {
                                dns_rdataset_disassociate(fevent->rdataset);
                        }
-                       fevent = NULL;
-                       isc_event_free(&event);
 
                        dns_resolver_destroyfetch(&fctx->nsfetch);
 
@@ -7344,7 +7387,7 @@ resume_dslookup(isc_task_t *task, isc_event_t *event) {
                        }
                        UNLOCK(&res->buckets[fctx->bucketnum].lock);
 
-                       fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+                       fctx_done(&fctx, DNS_R_SERVFAIL);
                        goto cleanup;
                }
                if (dns_rdataset_isassociated(
@@ -7362,8 +7405,6 @@ resume_dslookup(isc_task_t *task, isc_event_t *event) {
                if (dns_rdataset_isassociated(fevent->rdataset)) {
                        dns_rdataset_disassociate(fevent->rdataset);
                }
-               fevent = NULL;
-               isc_event_free(&event);
 
                LOCK(&res->buckets[fctx->bucketnum].lock);
                if (SHUTTINGDOWN(fctx)) {
@@ -7375,12 +7416,11 @@ resume_dslookup(isc_task_t *task, isc_event_t *event) {
 
                FCTXTRACE("continuing to look for parent's NS records");
 
-               fctx_attach(fctx, &ev_fctx);
-
+               fctx_addref(fctx);
                result = dns_resolver_createfetch(
                        res, fctx->nsname, dns_rdatatype_ns, domain, nsrdataset,
                        NULL, NULL, 0, fctx->options, 0, NULL, task,
-                       resume_dslookup, ev_fctx, &fctx->nsrrset, NULL,
+                       resume_dslookup, fctx, &fctx->nsrrset, NULL,
                        &fctx->nsfetch);
                /*
                 * fevent->rdataset (a.k.a. fctx->nsrrset) must not be
@@ -7391,19 +7431,18 @@ resume_dslookup(isc_task_t *task, isc_event_t *event) {
                        if (result == DNS_R_DUPLICATE) {
                                result = DNS_R_SERVFAIL;
                        }
-                       fctx_detach(&ev_fctx);
-                       fctx_done(fctx, result, __LINE__);
+                       fctx_unref(fctx);
+                       fctx_done_detach(&fctx, result);
                }
        }
 
 cleanup:
-       INSIST(event == NULL);
-       INSIST(fevent == NULL);
        if (dns_rdataset_isassociated(&nameservers)) {
                dns_rdataset_disassociate(&nameservers);
        }
 
-       fctx_detach(&fctx);
+       isc_event_free(&event);
+       fctx_detach(&ds_fctx);
 }
 
 static void
@@ -9609,7 +9648,7 @@ rctx_nextserver(respctx_t *rctx, dns_message_t *message,
                dcname = dns_fixedname_initname(&founddc);
 
                if (result != ISC_R_SUCCESS) {
-                       fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+                       fctx_done_detach(&rctx->fctx, DNS_R_SERVFAIL);
                        return;
                }
                if (dns_rdatatype_atparent(fctx->type)) {
@@ -9625,7 +9664,7 @@ rctx_nextserver(respctx_t *rctx, dns_message_t *message,
                        findoptions, true, true, &fctx->nameservers, NULL);
                if (result != ISC_R_SUCCESS) {
                        FCTXTRACE("couldn't find a zonecut");
-                       fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+                       fctx_done_detach(&rctx->fctx, DNS_R_SERVFAIL);
                        return;
                }
                if (!dns_name_issubdomain(fname, fctx->domain)) {
@@ -9634,7 +9673,7 @@ rctx_nextserver(respctx_t *rctx, dns_message_t *message,
                         * QDOMAIN.
                         */
                        FCTXTRACE("nameservers now above QDOMAIN");
-                       fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+                       fctx_done_detach(&rctx->fctx, DNS_R_SERVFAIL);
                        return;
                }
 
@@ -9645,7 +9684,7 @@ rctx_nextserver(respctx_t *rctx, dns_message_t *message,
 
                result = fcount_incr(fctx, true);
                if (result != ISC_R_SUCCESS) {
-                       fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+                       fctx_done_detach(&rctx->fctx, DNS_R_SERVFAIL);
                        return;
                }
                fctx->ns_ttl = fctx->nameservers.ttl;
@@ -9671,14 +9710,14 @@ rctx_nextserver(respctx_t *rctx, dns_message_t *message,
  */
 static void
 rctx_resend(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo) {
-       isc_result_t result;
        fetchctx_t *fctx = rctx->fctx;
+       isc_result_t result;
 
        FCTXTRACE("resend");
        inc_stats(fctx->res, dns_resstatscounter_retry);
        result = fctx_query(fctx, addrinfo, rctx->retryopts);
        if (result != ISC_R_SUCCESS) {
-               fctx_done(fctx, result, __LINE__);
+               fctx_done_detach(&rctx->fctx, result);
        }
 }
 
@@ -9711,9 +9750,8 @@ static void
 rctx_chaseds(respctx_t *rctx, dns_message_t *message,
             dns_adbaddrinfo_t *addrinfo, isc_result_t result) {
        fetchctx_t *fctx = rctx->fctx;
-       unsigned int n;
-       fetchctx_t *ev_fctx = NULL;
        isc_task_t *task = NULL;
+       unsigned int n;
 
        add_bad(fctx, message, addrinfo, result, rctx->broken_type);
        fctx_cancelqueries(fctx, true, false);
@@ -9724,19 +9762,18 @@ rctx_chaseds(respctx_t *rctx, dns_message_t *message,
 
        FCTXTRACE("suspending DS lookup to find parent's NS records");
 
-       fctx_attach(fctx, &ev_fctx);
-
+       fctx_addref(fctx);
        task = fctx->res->buckets[fctx->bucketnum].task;
        result = dns_resolver_createfetch(
                fctx->res, fctx->nsname, dns_rdatatype_ns, NULL, NULL, NULL,
-               NULL, 0, fctx->options, 0, NULL, task, resume_dslookup, ev_fctx,
+               NULL, 0, fctx->options, 0, NULL, task, resume_dslookup, fctx,
                &fctx->nsrrset, NULL, &fctx->nsfetch);
        if (result != ISC_R_SUCCESS) {
                if (result == DNS_R_DUPLICATE) {
                        result = DNS_R_SERVFAIL;
                }
-               fctx_detach(&ev_fctx);
-               fctx_done(fctx, result, __LINE__);
+               fctx_detach(&fctx);
+               fctx_done_detach(&rctx->fctx, result);
        }
 }
 
@@ -9772,7 +9809,7 @@ rctx_done(respctx_t *rctx, isc_result_t result) {
        {
                fctx_cancelquery(&query, rctx->finish, rctx->no_response,
                                 false);
-               fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+               fctx_done_detach(&rctx->fctx, DNS_R_SERVFAIL);
                goto detach;
        }
 #endif /* ifdef ENABLE_AFL */
@@ -9816,7 +9853,7 @@ rctx_done(respctx_t *rctx, isc_result_t result) {
                /*
                 * We're done.
                 */
-               fctx_done(fctx, result, __LINE__);
+               fctx_done_detach(&rctx->fctx, result);
        }
 
 detach:
@@ -10708,7 +10745,6 @@ dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name,
        unsigned int spillat;
        unsigned int spillatmin;
        bool dodestroy = false;
-       fetchctx_t *ev_fctx = NULL;
 
        UNUSED(forwarders);
 
@@ -10817,10 +10853,10 @@ dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name,
                         * Launch this fctx.
                         */
                        event = &fctx->control_event;
-                       fctx_attach(fctx, &ev_fctx);
+                       fctx_addref(fctx);
                        ISC_EVENT_INIT(event, sizeof(*event), 0, NULL,
-                                      DNS_EVENT_FETCHCONTROL, fctx_start,
-                                      ev_fctx, NULL, NULL, NULL);
+                                      DNS_EVENT_FETCHCONTROL, fctx_start, fctx,
+                                      NULL, NULL, NULL);
                        isc_task_send(res->buckets[bucketnum].task, &event);
                } else {
                        dodestroy = true;
@@ -10847,10 +10883,9 @@ unlock:
 
 void
 dns_resolver_cancelfetch(dns_fetch_t *fetch) {
-       fetchctx_t *fctx;
-       dns_resolver_t *res;
-       dns_fetchevent_t *event, *next_event;
-       isc_task_t *etask;
+       fetchctx_t *fctx = NULL;
+       dns_resolver_t *res = NULL;
+       dns_fetchevent_t *event = NULL;
 
        REQUIRE(DNS_FETCH_VALID(fetch));
        fctx = fetch->private;
@@ -10866,8 +10901,8 @@ dns_resolver_cancelfetch(dns_fetch_t *fetch) {
         * to those for other fetches that have joined the same
         * fctx) and send it with result = ISC_R_CANCELED.
         */
-       event = NULL;
        if (fctx->state != fetchstate_done) {
+               dns_fetchevent_t *next_event = NULL;
                for (event = ISC_LIST_HEAD(fctx->events); event != NULL;
                     event = next_event) {
                        next_event = ISC_LIST_NEXT(event, ev_link);
@@ -10878,7 +10913,7 @@ dns_resolver_cancelfetch(dns_fetch_t *fetch) {
                }
        }
        if (event != NULL) {
-               etask = event->ev_sender;
+               isc_task_t *etask = event->ev_sender;
                event->ev_sender = fctx;
                event->result = ISC_R_CANCELED;
                isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event));
@@ -10893,10 +10928,9 @@ dns_resolver_cancelfetch(dns_fetch_t *fetch) {
 
 void
 dns_resolver_destroyfetch(dns_fetch_t **fetchp) {
-       dns_fetch_t *fetch;
-       dns_resolver_t *res;
-       dns_fetchevent_t *event, *next_event;
-       fetchctx_t *fctx;
+       dns_fetch_t *fetch = NULL;
+       dns_resolver_t *res = NULL;
+       fetchctx_t *fctx = NULL;
        unsigned int bucketnum;
 
        REQUIRE(fetchp != NULL);
@@ -10917,6 +10951,7 @@ dns_resolver_destroyfetch(dns_fetch_t **fetchp) {
         * trying to destroy the fetch.
         */
        if (fctx->state != fetchstate_done) {
+               dns_fetchevent_t *event = NULL, *next_event = NULL;
                for (event = ISC_LIST_HEAD(fctx->events); event != NULL;
                     event = next_event) {
                        next_event = ISC_LIST_NEXT(event, ev_link);