]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Implement proper reference counting in dns_validator
authorOndřej Surý <ondrej@isc.org>
Thu, 16 Feb 2023 13:37:55 +0000 (14:37 +0100)
committerOndřej Surý <ondrej@isc.org>
Fri, 17 Feb 2023 06:18:25 +0000 (07:18 +0100)
use reference counting in dns_validator to prevent use after free.

lib/dns/include/dns/validator.h
lib/dns/resolver.c
lib/dns/validator.c

index 9400c216d60253fccb9f9870901d9ddf3db62d40..ccd4d9405e59c52d268d85efd046c77179f9ca08 100644 (file)
 
 #include <stdbool.h>
 
+#include <isc/job.h>
 #include <isc/lang.h>
 #include <isc/mutex.h>
+#include <isc/refcount.h>
 
 #include <dns/fixedname.h>
 #include <dns/rdataset.h>
  * whatever purpose the client desires.
  */
 struct dns_validator {
-       /* Unlocked. */
-       unsigned int magic;
-       isc_mutex_t  lock;
-       dns_view_t  *view;
+       /* Unlocked: */
+       unsigned int   magic;
+       isc_mutex_t    lock;
+       dns_view_t    *view;
+       isc_loopmgr_t *loopmgr;
+
+       isc_refcount_t references;
 
        /* Name and type of the response to be validated. */
        dns_name_t     *name;
        dns_rdatatype_t type;
 
-       /* Locked by lock. */
-       unsigned int       options;
-       unsigned int       attributes;
-       dns_fetch_t       *fetch;
-       dns_validator_t   *subvalidator;
-       dns_validator_t   *parent;
-       dns_keytable_t    *keytable;
-       dst_key_t         *key;
-       dns_rdata_rrsig_t *siginfo;
-       isc_loop_t        *loop;
-       isc_job_cb         cb;
-       void              *arg;
-       unsigned int       labels;
-       dns_rdataset_t    *nxset;
-       dns_rdataset_t    *keyset;
-       dns_rdataset_t    *dsset;
-       dns_rdataset_t     fdsset;
-       dns_rdataset_t     frdataset;
-       dns_rdataset_t     fsigrdataset;
-       dns_fixedname_t    fname;
-       dns_fixedname_t    wild;
-       dns_fixedname_t    closest;
-       ISC_LINK(dns_validator_t) link;
-       bool          mustbesecure;
-       unsigned int  depth;
-       unsigned int  authcount;
-       unsigned int  authfail;
-       isc_stdtime_t start;
+       /*
+        * Callback and argument to use to inform the caller
+        * that validation is complete.
+        */
+       isc_job_cb cb;
+       void      *arg;
+
+       /* Locked by 'lock': */
+       /* Validation options (_DEFER, _NONTA, etc). */
+       unsigned int options;
 
        /*
         * Results of a completed validation.
@@ -136,6 +123,31 @@ struct dns_validator {
         * Answer is secure.
         */
        bool secure;
+
+       /* Internal validator state */
+       unsigned int       attributes;
+       dns_fetch_t       *fetch;
+       dns_validator_t   *subvalidator;
+       dns_validator_t   *parent;
+       dns_keytable_t    *keytable;
+       dst_key_t         *key;
+       dns_rdata_rrsig_t *siginfo;
+       unsigned int       labels;
+       dns_rdataset_t    *nxset;
+       dns_rdataset_t    *keyset;
+       dns_rdataset_t    *dsset;
+       dns_rdataset_t     fdsset;
+       dns_rdataset_t     frdataset;
+       dns_rdataset_t     fsigrdataset;
+       dns_fixedname_t    fname;
+       dns_fixedname_t    wild;
+       dns_fixedname_t    closest;
+       ISC_LINK(dns_validator_t) link;
+       bool          mustbesecure;
+       unsigned int  depth;
+       unsigned int  authcount;
+       unsigned int  authfail;
+       isc_stdtime_t start;
 };
 
 /*%
@@ -152,7 +164,7 @@ isc_result_t
 dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
                     dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
                     dns_message_t *message, unsigned int options,
-                    isc_loop_t *loop, isc_job_cb cb, void *arg,
+                    isc_loopmgr_t *loop, isc_job_cb cb, void *arg,
                     dns_validator_t **validatorp);
 /*%<
  * Start a DNSSEC validation.
@@ -221,10 +233,24 @@ dns_validator_destroy(dns_validator_t **validatorp);
  * Requires:
  *\li  '*validatorp' points to a valid DNSSEC validator.
  * \li The validator must have completed and sent its completion
- *     event.
+ *     event.
  *
  * Ensures:
  *\li  All resources used by the validator are freed.
  */
 
+#if DNS_VALIDATOR_TRACE
+#define dns_validator_ref(ptr) \
+       dns_validator__ref(ptr, __func__, __FILE__, __LINE__)
+#define dns_validator_unref(ptr) \
+       dns_validator__unref(ptr, __func__, __FILE__, __LINE__)
+#define dns_validator_attach(ptr, ptrp) \
+       dns_validator__attach(ptr, ptrp, __func__, __FILE__, __LINE__)
+#define dns_validator_detach(ptrp) \
+       dns_validator__detach(ptrp, __func__, __FILE__, __LINE__)
+ISC_REFCOUNT_TRACE_DECL(dns_validator);
+#else
+ISC_REFCOUNT_DECL(dns_validator);
+#endif
+
 ISC_LANG_ENDDECLS
index ed31dd9f53f136470a75a59cd53cc9ce9c9c86f4..f6b6f64d102f4c2190c8e16c009044d6bbfd84a4 100644 (file)
@@ -961,8 +961,7 @@ set_stats(dns_resolver_t *res, isc_statscounter_t counter, uint64_t val) {
 static isc_result_t
 valcreate(fetchctx_t *fctx, dns_message_t *message, dns_adbaddrinfo_t *addrinfo,
          dns_name_t *name, dns_rdatatype_t type, dns_rdataset_t *rdataset,
-         dns_rdataset_t *sigrdataset, unsigned int valoptions,
-         isc_loop_t *loop) {
+         dns_rdataset_t *sigrdataset, unsigned int valoptions) {
        dns_validator_t *validator = NULL;
        dns_valarg_t *valarg = NULL;
        isc_result_t result;
@@ -980,9 +979,9 @@ valcreate(fetchctx_t *fctx, dns_message_t *message, dns_adbaddrinfo_t *addrinfo,
                valoptions &= ~DNS_VALIDATOR_DEFER;
        }
 
-       result = dns_validator_create(fctx->res->view, name, type, rdataset,
-                                     sigrdataset, message, valoptions, loop,
-                                     validated, valarg, &validator);
+       result = dns_validator_create(
+               fctx->res->view, name, type, rdataset, sigrdataset, message,
+               valoptions, fctx->res->loopmgr, validated, valarg, &validator);
        RUNTIME_CHECK(result == ISC_R_SUCCESS);
        inc_stats(fctx->res, dns_resstatscounter_val);
        if ((valoptions & DNS_VALIDATOR_DEFER) == 0) {
@@ -6314,8 +6313,7 @@ cache_name(fetchctx_t *fctx, dns_name_t *name, dns_message_t *message,
                                        result = valcreate(
                                                fctx, message, addrinfo, name,
                                                rdataset->type, rdataset,
-                                               sigrdataset, valoptions,
-                                               fctx->loop);
+                                               sigrdataset, valoptions);
                                }
                        } else if (CHAINING(rdataset)) {
                                if (rdataset->type == dns_rdatatype_cname) {
@@ -6423,8 +6421,7 @@ cache_name(fetchctx_t *fctx, dns_name_t *name, dns_message_t *message,
                }
 
                result = valcreate(fctx, message, addrinfo, name, vtype,
-                                  valrdataset, valsigrdataset, valoptions,
-                                  fctx->loop);
+                                  valrdataset, valsigrdataset, valoptions);
        }
 
        if (result == ISC_R_SUCCESS && have_answer) {
@@ -6646,7 +6643,7 @@ ncache_message(fetchctx_t *fctx, dns_message_t *message,
                 * Do negative response validation.
                 */
                result = valcreate(fctx, message, addrinfo, name, fctx->type,
-                                  NULL, NULL, valoptions, fctx->loop);
+                                  NULL, NULL, valoptions);
                /*
                 * If validation is necessary, return now.  Otherwise
                 * continue to process the message, letting the
index 3694e4463256e866e14f9d23706231aa2011ecc2..075d28ad72dd7059323f5605f12129ac9634a6c1 100644 (file)
 
 #include <isc/async.h>
 #include <isc/base32.h>
+#include <isc/job.h>
 #include <isc/md.h>
 #include <isc/mem.h>
+#include <isc/refcount.h>
 #include <isc/result.h>
 #include <isc/string.h>
 #include <isc/util.h>
@@ -63,7 +65,6 @@
 #define VALIDATOR_MAGIC           ISC_MAGIC('V', 'a', 'l', '?')
 #define VALID_VALIDATOR(v) ISC_MAGIC_VALID(v, VALIDATOR_MAGIC)
 
-#define VALATTR_SHUTDOWN 0x0001 /*%< Shutting down. */
 #define VALATTR_CANCELED 0x0002 /*%< Canceled. */
 #define VALATTR_TRIEDVERIFY                                    \
        0x0004                    /*%< We have found a key and \
@@ -97,7 +98,6 @@
 #define FOUNDCLOSEST(val)    ((val->attributes & VALATTR_FOUNDCLOSEST) != 0)
 #define FOUNDOPTOUT(val)     ((val->attributes & VALATTR_FOUNDOPTOUT) != 0)
 
-#define SHUTDOWN(v) (((v)->attributes & VALATTR_SHUTDOWN) != 0)
 #define CANCELED(v) (((v)->attributes & VALATTR_CANCELED) != 0)
 #define COMPLETE(v) (((v)->attributes & VALATTR_COMPLETE) != 0)
 
 #define NXDOMAIN(r) (((r)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0)
 
 static void
-destroy(dns_validator_t *val);
+destroy_validator(dns_validator_t *val);
 
 static isc_result_t
 select_signing_key(dns_validator_t *val, dns_rdataset_t *rdataset);
@@ -203,6 +203,13 @@ marksecure(dns_validator_t *val) {
        val->secure = true;
 }
 
+static void
+validator_done_cb(void *arg) {
+       dns_validator_t *val = arg;
+       val->cb(val);
+       dns_validator_detach(&val);
+}
+
 /*
  * Validator 'val' is finished; send the completion event to the loop
  * that called dns_validator_create(), with result `result`.
@@ -217,26 +224,9 @@ validator_done(dns_validator_t *val, isc_result_t result) {
 
        val->attributes |= VALATTR_COMPLETE;
        val->result = result;
-       isc_async_run(val->loop, val->cb, val);
-}
-
-/*
- * Called when deciding whether to destroy validator 'val'.
- */
-static bool
-exit_check(dns_validator_t *val) {
-       /*
-        * Caller must be holding the lock.
-        */
-       if (!SHUTDOWN(val)) {
-               return (false);
-       }
 
-       if (val->fetch != NULL || val->subvalidator != NULL) {
-               return (false);
-       }
-
-       return (true);
+       dns_validator_ref(val);
+       isc_job_run(val->loopmgr, validator_done_cb, val);
 }
 
 /*%
@@ -372,7 +362,6 @@ fetch_callback_dnskey(void *arg) {
        isc_result_t result;
        isc_result_t saved_result;
        dns_fetch_t *fetch = NULL;
-       bool want_destroy;
 
        INSIST(resp->type == FETCHDONE);
 
@@ -439,17 +428,13 @@ fetch_callback_dnskey(void *arg) {
                        validator_done(val, DNS_R_BROKENCHAIN);
                }
        }
-
-       want_destroy = exit_check(val);
        UNLOCK(&val->lock);
 
        if (fetch != NULL) {
                dns_resolver_destroyfetch(&fetch);
        }
 
-       if (want_destroy) {
-               destroy(val);
-       }
+       dns_validator_detach(&val);
 }
 
 /*%
@@ -464,7 +449,7 @@ fetch_callback_ds(void *arg) {
        isc_result_t eresult = resp->result;
        isc_result_t result;
        dns_fetch_t *fetch = NULL;
-       bool want_destroy, trustchain;
+       bool trustchain;
 
        INSIST(resp->type == FETCHDONE);
 
@@ -592,16 +577,13 @@ fetch_callback_ds(void *arg) {
 done:
 
        isc_mem_putanddetach(&resp->mctx, resp, sizeof(*resp));
-       want_destroy = exit_check(val);
        UNLOCK(&val->lock);
 
        if (fetch != NULL) {
                dns_resolver_destroyfetch(&fetch);
        }
 
-       if (want_destroy) {
-               destroy(val);
-       }
+       dns_validator_detach(&val);
 }
 
 /*%
@@ -616,9 +598,9 @@ validator_callback_dnskey(void *arg) {
        isc_result_t result;
        isc_result_t eresult = subvalidator->result;
        isc_result_t saved_result;
-       bool want_destroy;
 
        val->subvalidator = NULL;
+       subvalidator->parent = NULL;
 
        validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_dnskey");
        LOCK(&val->lock);
@@ -658,13 +640,10 @@ validator_callback_dnskey(void *arg) {
                validator_done(val, DNS_R_BROKENCHAIN);
        }
 
-       dns_validator_destroy(&subvalidator);
-
-       want_destroy = exit_check(val);
        UNLOCK(&val->lock);
-       if (want_destroy) {
-               destroy(val);
-       }
+
+       dns_validator_destroy(&subvalidator);
+       dns_validator_detach(&val);
 }
 
 /*%
@@ -678,9 +657,9 @@ validator_callback_ds(void *arg) {
        dns_validator_t *val = subvalidator->parent;
        isc_result_t result;
        isc_result_t eresult = subvalidator->result;
-       bool want_destroy;
 
        val->subvalidator = NULL;
+       subvalidator->parent = NULL;
 
        validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_ds");
        LOCK(&val->lock);
@@ -720,14 +699,10 @@ validator_callback_ds(void *arg) {
                              isc_result_totext(eresult));
                validator_done(val, DNS_R_BROKENCHAIN);
        }
+       UNLOCK(&val->lock);
 
        dns_validator_destroy(&subvalidator);
-
-       want_destroy = exit_check(val);
-       UNLOCK(&val->lock);
-       if (want_destroy) {
-               destroy(val);
-       }
+       dns_validator_detach(&val);
 }
 
 /*%
@@ -741,7 +716,6 @@ validator_callback_cname(void *arg) {
        dns_validator_t *val = subvalidator->parent;
        isc_result_t result;
        isc_result_t eresult = subvalidator->result;
-       bool want_destroy;
 
        INSIST((val->attributes & VALATTR_INSECURITY) != 0);
 
@@ -768,13 +742,10 @@ validator_callback_cname(void *arg) {
                validator_done(val, DNS_R_BROKENCHAIN);
        }
 
-       dns_validator_destroy(&subvalidator);
-
-       want_destroy = exit_check(val);
        UNLOCK(&val->lock);
-       if (want_destroy) {
-               destroy(val);
-       }
+
+       dns_validator_destroy(&subvalidator);
+       dns_validator_detach(&val);
 }
 
 /*%
@@ -791,7 +762,6 @@ validator_callback_nsec(void *arg) {
        dns_rdataset_t *rdataset = subvalidator->rdataset;
        isc_result_t result = subvalidator->result;
        bool exists, data;
-       bool want_destroy;
 
        val->subvalidator = NULL;
 
@@ -871,13 +841,10 @@ validator_callback_nsec(void *arg) {
                }
        }
 
-       dns_validator_destroy(&subvalidator);
-
-       want_destroy = exit_check(val);
        UNLOCK(&val->lock);
-       if (want_destroy) {
-               destroy(val);
-       }
+
+       dns_validator_destroy(&subvalidator);
+       dns_validator_detach(&val);
 }
 
 /*%
@@ -993,10 +960,12 @@ create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
        }
 
        validator_logcreate(val, name, type, caller, "fetch");
+
+       dns_validator_ref(val);
        return (dns_resolver_createfetch(
                val->view->resolver, name, type, NULL, NULL, NULL, NULL, 0,
-               fopts, 0, NULL, val->loop, callback, val, &val->frdataset,
-               &val->fsigrdataset, &val->fetch));
+               fopts, 0, NULL, isc_loop_current(val->loopmgr), callback, val,
+               &val->frdataset, &val->fsigrdataset, &val->fetch));
 }
 
 /*%
@@ -1026,10 +995,10 @@ create_validator(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
 
        validator_logcreate(val, name, type, caller, "validator");
        result = dns_validator_create(val->view, name, type, rdataset, sig,
-                                     NULL, vopts, val->loop, cb, val,
+                                     NULL, vopts, val->loopmgr, cb, val,
                                      &val->subvalidator);
        if (result == ISC_R_SUCCESS) {
-               val->subvalidator->parent = val;
+               dns_validator_attach(val, &val->subvalidator->parent);
                val->subvalidator->depth = val->depth + 1;
        }
        return (result);
@@ -1239,6 +1208,7 @@ seek_dnskey(dns_validator_t *val) {
                                      dns_rdatatype_dnskey,
                                      fetch_callback_dnskey, "seek_dnskey");
                if (result != ISC_R_SUCCESS) {
+                       dns_validator_detach(&val);
                        return (result);
                }
                return (DNS_R_WAIT);
@@ -1668,6 +1638,7 @@ get_dsset(dns_validator_t *val, dns_name_t *tname, isc_result_t *resp) {
                                      fetch_callback_ds, "validate_dnskey");
                *resp = DNS_R_WAIT;
                if (result != ISC_R_SUCCESS) {
+                       dns_validator_detach(&val);
                        *resp = result;
                }
                return (ISC_R_COMPLETE);
@@ -2923,7 +2894,6 @@ out:
 static void
 validator_start(void *arg) {
        dns_validator_t *val = (dns_validator_t *)arg;
-       bool want_destroy = false;
        isc_result_t result = ISC_R_FAILURE;
 
        if (CANCELED(val)) {
@@ -3014,21 +2984,19 @@ validator_start(void *arg) {
        }
 
        if (result != DNS_R_WAIT) {
-               want_destroy = exit_check(val);
                validator_done(val, result);
        }
 
        UNLOCK(&val->lock);
-       if (want_destroy) {
-               destroy(val);
-       }
+
+       dns_validator_detach(&val);
 }
 
 isc_result_t
 dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
                     dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
                     dns_message_t *message, unsigned int options,
-                    isc_loop_t *loop, isc_job_cb cb, void *arg,
+                    isc_loopmgr_t *loopmgr, isc_job_cb cb, void *arg,
                     dns_validator_t **validatorp) {
        isc_result_t result = ISC_R_FAILURE;
        dns_validator_t *val = NULL;
@@ -3046,9 +3014,12 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
                                  .type = type,
                                  .options = options,
                                  .link = ISC_LINK_INITIALIZER,
-                                 .loop = loop,
+                                 .loopmgr = loopmgr,
                                  .cb = cb,
                                  .arg = arg };
+
+       isc_refcount_init(&val->references, 1);
+
        if (message != NULL) {
                dns_message_attach(message, &val->message);
        }
@@ -3070,7 +3041,8 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
        val->magic = VALIDATOR_MAGIC;
 
        if ((options & DNS_VALIDATOR_DEFER) == 0) {
-               isc_async_run(loop, validator_start, val);
+               dns_validator_ref(val);
+               isc_job_run(val->loopmgr, validator_start, val);
        }
 
        *validatorp = val;
@@ -3098,7 +3070,8 @@ dns_validator_send(dns_validator_t *validator) {
        validator->options &= ~DNS_VALIDATOR_DEFER;
        UNLOCK(&validator->lock);
 
-       isc_async_run(validator->loop, validator_start, validator);
+       dns_validator_ref(validator);
+       isc_job_run(validator->loopmgr, validator_start, validator);
 }
 
 void
@@ -3127,11 +3100,11 @@ dns_validator_cancel(dns_validator_t *validator) {
 }
 
 static void
-destroy(dns_validator_t *val) {
+destroy_validator(dns_validator_t *val) {
        isc_mem_t *mctx = NULL;
 
-       REQUIRE(SHUTDOWN(val));
        REQUIRE(val->fetch == NULL);
+       REQUIRE(val->subvalidator == NULL);
 
        val->magic = 0;
        if (val->key != NULL) {
@@ -3159,7 +3132,6 @@ destroy(dns_validator_t *val) {
 void
 dns_validator_destroy(dns_validator_t **validatorp) {
        dns_validator_t *val = NULL;
-       bool want_destroy = false;
 
        REQUIRE(validatorp != NULL);
 
@@ -3169,15 +3141,10 @@ dns_validator_destroy(dns_validator_t **validatorp) {
        REQUIRE(VALID_VALIDATOR(val));
 
        LOCK(&val->lock);
-
-       val->attributes |= VALATTR_SHUTDOWN;
        validator_log(val, ISC_LOG_DEBUG(4), "dns_validator_destroy");
-
-       want_destroy = exit_check(val);
        UNLOCK(&val->lock);
-       if (want_destroy) {
-               destroy(val);
-       }
+
+       dns_validator_detach(&val);
 }
 
 static void
@@ -3256,3 +3223,9 @@ validator_logcreate(dns_validator_t *val, dns_name_t *name,
        validator_log(val, ISC_LOG_DEBUG(9), "%s: creating %s for %s %s",
                      caller, operation, namestr, typestr);
 }
+
+#if DNS_VALIDATOR_TRACE
+ISC_REFCOUNT_TRACE_IMPL(dns_validator, destroy_validator);
+#else
+ISC_REFCOUNT_IMPL(dns_validator, destroy_validator);
+#endif