2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-30 22:15:20 +00:00

Fix the fetch context hash table lock ordering

The order of the fetch context hash table rwlock and the individual
fetch context was reversed when calling the release_fctx() function.
This was causing a problem when iterating the hash table, and thus the
ordering has been corrected in a way that the hash table rwlock is now
always locked on the outside and the fctx lock is the interior lock.
This commit is contained in:
Ondřej Surý
2025-02-19 06:28:46 +01:00
parent b9e3cd5d2a
commit cf078fadeb

View File

@@ -350,7 +350,6 @@ struct fetchctx {
/*% Locked by lock. */ /*% Locked by lock. */
isc_mutex_t lock; isc_mutex_t lock;
fetchstate_t state; fetchstate_t state;
bool hashed;
bool cloned; bool cloned;
bool spilled; bool spilled;
ISC_LIST(dns_fetchresponse_t) resps; ISC_LIST(dns_fetchresponse_t) resps;
@@ -588,9 +587,6 @@ struct dns_resolver {
/* Locked by primelock. */ /* Locked by primelock. */
dns_fetch_t *primefetch; dns_fetch_t *primefetch;
/* Atomic. */
atomic_uint_fast32_t nfctx;
uint32_t nloops; uint32_t nloops;
isc_mempool_t **namepools; isc_mempool_t **namepools;
@@ -706,8 +702,6 @@ get_attached_fctx(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name,
dns_rdataset_t *nameservers, const isc_sockaddr_t *client, dns_rdataset_t *nameservers, const isc_sockaddr_t *client,
unsigned int options, unsigned int depth, isc_counter_t *qc, unsigned int options, unsigned int depth, isc_counter_t *qc,
isc_counter_t *gqc, fetchctx_t **fctxp, bool *new_fctx); isc_counter_t *gqc, fetchctx_t **fctxp, bool *new_fctx);
static void
release_fctx(fetchctx_t *fctx);
/*% /*%
* The structure and functions defined below implement the resolver * The structure and functions defined below implement the resolver
@@ -1641,6 +1635,25 @@ fctx_sendevents(fetchctx_t *fctx, isc_result_t result) {
} }
} }
static uint32_t
fctx_hash(fetchctx_t *fctx) {
isc_hash32_t hash32;
isc_hash32_init(&hash32);
isc_hash32_hash(&hash32, fctx->name->ndata, fctx->name->length, false);
isc_hash32_hash(&hash32, &fctx->options, sizeof(fctx->options), true);
isc_hash32_hash(&hash32, &fctx->type, sizeof(fctx->type), true);
return isc_hash32_finalize(&hash32);
}
static bool
fctx_match(void *node, const void *key) {
const fetchctx_t *fctx0 = node;
const fetchctx_t *fctx1 = key;
return fctx0->options == fctx1->options && fctx0->type == fctx1->type &&
dns_name_equal(fctx0->name, fctx1->name);
}
static bool static bool
fctx__done(fetchctx_t *fctx, isc_result_t result, const char *func, fctx__done(fetchctx_t *fctx, isc_result_t result, const char *func,
const char *file, unsigned int line) { const char *file, unsigned int line) {
@@ -1668,11 +1681,15 @@ fctx__done(fetchctx_t *fctx, isc_result_t result, const char *func,
return false; return false;
} }
fctx->state = fetchstate_done; fctx->state = fetchstate_done;
release_fctx(fctx);
FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
UNLOCK(&fctx->lock); UNLOCK(&fctx->lock);
/* The fctx will get deleted either here or in get_attached_fctx() */
RWLOCK(&fctx->res->fctxs_lock, isc_rwlocktype_write);
(void)isc_hashmap_delete(fctx->res->fctxs, fctx_hash(fctx), match_ptr,
fctx);
RWUNLOCK(&fctx->res->fctxs_lock, isc_rwlocktype_write);
if (result == ISC_R_SUCCESS) { if (result == ISC_R_SUCCESS) {
if (fctx->qmin_warning != ISC_R_SUCCESS) { if (fctx->qmin_warning != ISC_R_SUCCESS) {
isc_log_write(DNS_LOGCATEGORY_LAME_SERVERS, isc_log_write(DNS_LOGCATEGORY_LAME_SERVERS,
@@ -4335,7 +4352,6 @@ fctx_destroy(fetchctx_t *fctx) {
dns_resolver_t *res = NULL; dns_resolver_t *res = NULL;
isc_sockaddr_t *sa = NULL, *next_sa = NULL; isc_sockaddr_t *sa = NULL, *next_sa = NULL;
struct tried *tried = NULL; struct tried *tried = NULL;
uint_fast32_t nfctx;
REQUIRE(VALID_FCTX(fctx)); REQUIRE(VALID_FCTX(fctx));
REQUIRE(ISC_LIST_EMPTY(fctx->resps)); REQUIRE(ISC_LIST_EMPTY(fctx->resps));
@@ -4354,9 +4370,6 @@ fctx_destroy(fetchctx_t *fctx) {
dec_stats(res, dns_resstatscounter_nfetch); dec_stats(res, dns_resstatscounter_nfetch);
nfctx = atomic_fetch_sub_release(&res->nfctx, 1);
INSIST(nfctx > 0);
/* Free bad */ /* Free bad */
for (sa = ISC_LIST_HEAD(fctx->bad); sa != NULL; sa = next_sa) { for (sa = ISC_LIST_HEAD(fctx->bad); sa != NULL; sa = next_sa) {
next_sa = ISC_LIST_NEXT(sa, link); next_sa = ISC_LIST_NEXT(sa, link);
@@ -4544,7 +4557,6 @@ fctx_create(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name,
isc_interval_t interval; isc_interval_t interval;
unsigned int findoptions = 0; unsigned int findoptions = 0;
char buf[DNS_NAME_FORMATSIZE + DNS_RDATATYPE_FORMATSIZE + 1]; char buf[DNS_NAME_FORMATSIZE + DNS_RDATATYPE_FORMATSIZE + 1];
uint_fast32_t nfctx;
isc_mem_t *mctx = isc_loop_getmctx(loop); isc_mem_t *mctx = isc_loop_getmctx(loop);
size_t p; size_t p;
@@ -4796,9 +4808,6 @@ fctx_create(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name,
fctx_minimize_qname(fctx); fctx_minimize_qname(fctx);
} }
nfctx = atomic_fetch_add_relaxed(&res->nfctx, 1);
INSIST(nfctx < UINT32_MAX);
inc_stats(res, dns_resstatscounter_nfetch); inc_stats(res, dns_resstatscounter_nfetch);
isc_timer_create(fctx->loop, fctx_expired, fctx, &fctx->timer); isc_timer_create(fctx->loop, fctx_expired, fctx, &fctx->timer);
@@ -7031,43 +7040,6 @@ ISC_REFCOUNT_TRACE_IMPL(fetchctx, fctx_destroy);
ISC_REFCOUNT_IMPL(fetchctx, fctx_destroy); ISC_REFCOUNT_IMPL(fetchctx, fctx_destroy);
#endif #endif
static uint32_t
fctx_hash(fetchctx_t *fctx) {
isc_hash32_t hash32;
isc_hash32_init(&hash32);
isc_hash32_hash(&hash32, fctx->name->ndata, fctx->name->length, false);
isc_hash32_hash(&hash32, &fctx->options, sizeof(fctx->options), true);
isc_hash32_hash(&hash32, &fctx->type, sizeof(fctx->type), true);
return isc_hash32_finalize(&hash32);
}
static bool
fctx_match(void *node, const void *key) {
const fetchctx_t *fctx0 = node;
const fetchctx_t *fctx1 = key;
return fctx0->options == fctx1->options && fctx0->type == fctx1->type &&
dns_name_equal(fctx0->name, fctx1->name);
}
/* Must be fctx locked */
static void
release_fctx(fetchctx_t *fctx) {
isc_result_t result;
dns_resolver_t *res = fctx->res;
if (!fctx->hashed) {
return;
}
RWLOCK(&res->fctxs_lock, isc_rwlocktype_write);
result = isc_hashmap_delete(res->fctxs, fctx_hash(fctx), match_ptr,
fctx);
INSIST(result == ISC_R_SUCCESS);
fctx->hashed = false;
RWUNLOCK(&res->fctxs_lock, isc_rwlocktype_write);
}
static void static void
resume_dslookup(void *arg) { resume_dslookup(void *arg) {
dns_fetchresponse_t *resp = (dns_fetchresponse_t *)arg; dns_fetchresponse_t *resp = (dns_fetchresponse_t *)arg;
@@ -9939,8 +9911,6 @@ dns_resolver__destroy(dns_resolver_t *res) {
RTRACE("destroy"); RTRACE("destroy");
REQUIRE(atomic_load_acquire(&res->nfctx) == 0);
res->magic = 0; res->magic = 0;
dns_nametree_detach(&res->algorithms); dns_nametree_detach(&res->algorithms);
@@ -10415,7 +10385,8 @@ again:
result = fctx_create(res, loop, name, type, domain, nameservers, result = fctx_create(res, loop, name, type, domain, nameservers,
client, options, depth, qc, gqc, &fctx); client, options, depth, qc, gqc, &fctx);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
goto unlock; RWUNLOCK(&res->fctxs_lock, locktype);
return result;
} }
UPGRADELOCK(&res->fctxs_lock, locktype); UPGRADELOCK(&res->fctxs_lock, locktype);
@@ -10425,22 +10396,33 @@ again:
fctx, &found); fctx, &found);
if (result == ISC_R_SUCCESS) { if (result == ISC_R_SUCCESS) {
*new_fctx = true; *new_fctx = true;
fctx->hashed = true;
} else { } else {
fctx_done_detach(&fctx, result); /*
* The fctx_done() tries to acquire the fctxs_lock.
* Destroy the newly created fetchctx directly.
*/
fctx->state = fetchstate_done;
isc_timer_destroy(&fctx->timer);
fetchctx_detach(&fctx);
fctx = found; fctx = found;
result = ISC_R_SUCCESS; result = ISC_R_SUCCESS;
} }
INSIST(result == ISC_R_SUCCESS);
break; break;
default: default:
UNREACHABLE(); UNREACHABLE();
} }
INSIST(result == ISC_R_SUCCESS);
fetchctx_ref(fctx); fetchctx_ref(fctx);
unlock:
RWUNLOCK(&res->fctxs_lock, locktype); /*
if (result == ISC_R_SUCCESS) { * We need to lock the fetch context before unlocking the hash table to
* prevent other threads from looking up this thread before it has been
* properly initialized and started.
*/
LOCK(&fctx->lock); LOCK(&fctx->lock);
RWUNLOCK(&res->fctxs_lock, locktype);
if (SHUTTINGDOWN(fctx) || fctx->cloned) { if (SHUTTINGDOWN(fctx) || fctx->cloned) {
/* /*
* This is the single place where fctx might get * This is the single place where fctx might get
@@ -10448,15 +10430,22 @@ unlock:
* double check whether fctxs is done (or cloned) and * double check whether fctxs is done (or cloned) and
* help with the release if the fctx has been cloned. * help with the release if the fctx has been cloned.
*/ */
release_fctx(fctx);
UNLOCK(&fctx->lock); UNLOCK(&fctx->lock);
/* The fctx will get deleted either here or in fctx__done() */
RWLOCK(&res->fctxs_lock, isc_rwlocktype_write);
(void)isc_hashmap_delete(res->fctxs, fctx_hash(fctx), match_ptr,
fctx);
RWUNLOCK(&res->fctxs_lock, isc_rwlocktype_write);
fetchctx_detach(&fctx); fetchctx_detach(&fctx);
goto again; goto again;
} }
INSIST(!SHUTTINGDOWN(fctx)); /*
* The function returns a locked fetch context,
*/
*fctxp = fctx; *fctxp = fctx;
}
return result; return result;
} }
@@ -10560,7 +10549,7 @@ dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name,
result = fctx_create(res, loop, name, type, domain, nameservers, result = fctx_create(res, loop, name, type, domain, nameservers,
client, options, depth, qc, gqc, &fctx); client, options, depth, qc, gqc, &fctx);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
goto unlock; goto fail;
} }
new_fctx = true; new_fctx = true;
} }