Race in dns_tsigkey_find()
I think there is a race condition which will trigger ISC_LINK_INSIST()
when dns_tsigkey_find()
is called twice for the same generated and expired key.
In the function dns_tsigkey_find()
in lib/dns/tsig.c
a lookup for a key is performed based on the name and algorithm. If the found key is expired it is removed from the key-ring.
These operation are protected by the \fvar{ring->lock} and depending on the access this lock is held in read or write mode.
isc_result_t
dns_tsigkey_find(dns_tsigkey_t **tsigkey, const dns_name_t *name,
const dns_name_t *algorithm, dns_tsig_keyring_t *ring) {
...
RWLOCK(&ring->lock, isc_rwlocktype_read);
key = NULL;
result = dns_rbt_findname(ring->keys, name, 0, NULL, (void *)&key);
...
if (key->inception != key->expire && isc_serial_lt(key->expire, now)) {
/*
* The key has expired.
*/
RWUNLOCK(&ring->lock, isc_rwlocktype_read);
RWLOCK(&ring->lock, isc_rwlocktype_write);
remove_fromring(key);
RWUNLOCK(&ring->lock, isc_rwlocktype_write);
return (ISC_R_NOTFOUND);
}
...
RWUNLOCK(&ring->lock, isc_rwlocktype_read);
adjust_lru(key);
*tsigkey = key;
return (ISC_R_SUCCESS);
}
When the process gets interrupted after the call to RWUNLOCK(&ring->lock, isc_rwlocktype_read)
before the call to RWLOCK(&ring->lock, isc_rwlocktype_write)
the call to remove_fromring()
will be performed twice.
For generated keys the function remove_fromring()
will try to unlink that key
twice, which will trigger ISC_LINK_INSIST(ISC_LINK_LINKED(elt, link))
.
static void
remove_fromring(dns_tsigkey_t *tkey) {
if (tkey->generated) {
ISC_LIST_UNLINK(tkey->ring->lru, tkey, link);
tkey->ring->generated--;
}
(void)dns_rbt_deletename(tkey->ring->keys, &tkey->name, false);
}