2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-29 13:38:26 +00:00

Merge branch '4055-improve-the-overmem-cache-cleaning-9.18' into 'security-bind-9.18'

[9.18] Improve RBT overmem cache cleaning

See merge request isc-private/bind9!527
This commit is contained in:
Michal Nowak 2023-06-09 12:54:04 +00:00
commit e9af3d15d8
4 changed files with 82 additions and 42 deletions

View File

@ -1,3 +1,7 @@
6190. [security] Improve the overmem cleaning process to prevent the
cache going over the configured limit. (CVE-2023-2828)
[GL #4055]
6188. [performance] Reduce memory consumption by allocating properly 6188. [performance] Reduce memory consumption by allocating properly
sized send buffers for stream-based transports. sized send buffers for stream-based transports.
[GL #4038] [GL #4038]

View File

@ -3932,6 +3932,11 @@ system.
default value of that option (90% of physical memory for each default value of that option (90% of physical memory for each
individual cache) may lead to memory exhaustion over time. individual cache) may lead to memory exhaustion over time.
.. note::
:any:`max-cache-size` does not work reliably for the maximum
amount of memory of 100 MB or lower.
Upon startup and reconfiguration, caches with a limited size Upon startup and reconfiguration, caches with a limited size
preallocate a small amount of memory (less than 1% of preallocate a small amount of memory (less than 1% of
:any:`max-cache-size` for a given view). This preallocation serves as an :any:`max-cache-size` for a given view). This preallocation serves as an

View File

@ -15,7 +15,14 @@ Notes for BIND 9.18.16
Security Fixes Security Fixes
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
- None. - The overmem cleaning process has been improved, to prevent the cache from
significantly exceeding the configured :any:`max-cache-size` limit.
(CVE-2023-2828)
ISC would like to thank Shoham Danino from Reichman University, Anat
Bremler-Barr from Tel-Aviv University, Yehuda Afek from Tel-Aviv University,
and Yuval Shavitt from Tel-Aviv University for bringing this vulnerability to
our attention. :gl:`#4055`
New Features New Features
~~~~~~~~~~~~ ~~~~~~~~~~~~

View File

@ -561,7 +561,7 @@ static void
expire_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, bool tree_locked, expire_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, bool tree_locked,
expire_t reason); expire_t reason);
static void static void
overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, isc_stdtime_t now, overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, size_t purgesize,
bool tree_locked); bool tree_locked);
static void static void
resign_insert(dns_rbtdb_t *rbtdb, int idx, rdatasetheader_t *newheader); resign_insert(dns_rbtdb_t *rbtdb, int idx, rdatasetheader_t *newheader);
@ -6795,6 +6795,16 @@ cleanup:
static dns_dbmethods_t zone_methods; static dns_dbmethods_t zone_methods;
static size_t
rdataset_size(rdatasetheader_t *header) {
if (!NONEXISTENT(header)) {
return (dns_rdataslab_size((unsigned char *)header,
sizeof(*header)));
}
return (sizeof(*header));
}
static isc_result_t static isc_result_t
addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
isc_stdtime_t now, dns_rdataset_t *rdataset, unsigned int options, isc_stdtime_t now, dns_rdataset_t *rdataset, unsigned int options,
@ -6959,7 +6969,8 @@ addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
} }
if (cache_is_overmem) { if (cache_is_overmem) {
overmem_purge(rbtdb, rbtnode->locknum, now, tree_locked); overmem_purge(rbtdb, rbtnode->locknum, rdataset_size(newheader),
tree_locked);
} }
NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
@ -6978,11 +6989,18 @@ addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
} }
header = isc_heap_element(rbtdb->heaps[rbtnode->locknum], 1); header = isc_heap_element(rbtdb->heaps[rbtnode->locknum], 1);
if (header != NULL && if (header != NULL) {
header->rdh_ttl + STALE_TTL(header, rbtdb) < dns_ttl_t rdh_ttl = header->rdh_ttl;
now - RBTDB_VIRTUAL)
{ /* Only account for stale TTL if cache is not overmem */
expire_header(rbtdb, header, tree_locked, expire_ttl); if (!cache_is_overmem) {
rdh_ttl += STALE_TTL(header, rbtdb);
}
if (rdh_ttl < now - RBTDB_VIRTUAL) {
expire_header(rbtdb, header, tree_locked,
expire_ttl);
}
} }
/* /*
@ -10122,52 +10140,58 @@ update_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, isc_stdtime_t now) {
ISC_LIST_PREPEND(rbtdb->rdatasets[header->node->locknum], header, link); ISC_LIST_PREPEND(rbtdb->rdatasets[header->node->locknum], header, link);
} }
static size_t
expire_lru_headers(dns_rbtdb_t *rbtdb, unsigned int locknum, size_t purgesize,
bool tree_locked) {
rdatasetheader_t *header, *header_prev;
size_t purged = 0;
for (header = ISC_LIST_TAIL(rbtdb->rdatasets[locknum]);
header != NULL && purged <= purgesize; header = header_prev)
{
header_prev = ISC_LIST_PREV(header, link);
/*
* Unlink the entry at this point to avoid checking it
* again even if it's currently used someone else and
* cannot be purged at this moment. This entry won't be
* referenced any more (so unlinking is safe) since the
* TTL was reset to 0.
*/
ISC_LIST_UNLINK(rbtdb->rdatasets[locknum], header, link);
size_t header_size = rdataset_size(header);
expire_header(rbtdb, header, tree_locked, expire_lru);
purged += header_size;
}
return (purged);
}
/*% /*%
* Purge some expired and/or stale (i.e. unused for some period) cache entries * Purge some stale (i.e. unused for some period - LRU based cleaning) cache
* under an overmem condition. To recover from this condition quickly, up to * entries under the overmem condition. To recover from this condition quickly,
* 2 entries will be purged. This process is triggered while adding a new * we cleanup entries up to the size of newly added rdata (passed as purgesize).
* entry, and we specifically avoid purging entries in the same LRU bucket as *
* the one to which the new entry will belong. Otherwise, we might purge * This process is triggered while adding a new entry, and we specifically avoid
* entries of the same name of different RR types while adding RRsets from a * purging entries in the same LRU bucket as the one to which the new entry will
* single response (consider the case where we're adding A and AAAA glue records * belong. Otherwise, we might purge entries of the same name of different RR
* of the same NS name). * types while adding RRsets from a single response (consider the case where
* we're adding A and AAAA glue records of the same NS name).
*/ */
static void static void
overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, isc_stdtime_t now, overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, size_t purgesize,
bool tree_locked) { bool tree_locked) {
rdatasetheader_t *header, *header_prev;
unsigned int locknum; unsigned int locknum;
int purgecount = 2; size_t purged = 0;
for (locknum = (locknum_start + 1) % rbtdb->node_lock_count; for (locknum = (locknum_start + 1) % rbtdb->node_lock_count;
locknum != locknum_start && purgecount > 0; locknum != locknum_start && purged <= purgesize;
locknum = (locknum + 1) % rbtdb->node_lock_count) locknum = (locknum + 1) % rbtdb->node_lock_count)
{ {
NODE_LOCK(&rbtdb->node_locks[locknum].lock, NODE_LOCK(&rbtdb->node_locks[locknum].lock,
isc_rwlocktype_write); isc_rwlocktype_write);
header = isc_heap_element(rbtdb->heaps[locknum], 1); purged += expire_lru_headers(rbtdb, locknum, purgesize - purged,
if (header && header->rdh_ttl < now - RBTDB_VIRTUAL) { tree_locked);
expire_header(rbtdb, header, tree_locked, expire_ttl);
purgecount--;
}
for (header = ISC_LIST_TAIL(rbtdb->rdatasets[locknum]);
header != NULL && purgecount > 0; header = header_prev)
{
header_prev = ISC_LIST_PREV(header, link);
/*
* Unlink the entry at this point to avoid checking it
* again even if it's currently used someone else and
* cannot be purged at this moment. This entry won't be
* referenced any more (so unlinking is safe) since the
* TTL was reset to 0.
*/
ISC_LIST_UNLINK(rbtdb->rdatasets[locknum], header,
link);
expire_header(rbtdb, header, tree_locked, expire_lru);
purgecount--;
}
NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, NODE_UNLOCK(&rbtdb->node_locks[locknum].lock,
isc_rwlocktype_write); isc_rwlocktype_write);