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:
commit
e9af3d15d8
4
CHANGES
4
CHANGES
@ -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]
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
100
lib/dns/rbtdb.c
100
lib/dns/rbtdb.c
@ -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,38 +10140,14 @@ 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
|
||||||
* Purge some expired and/or stale (i.e. unused for some period) cache entries
|
expire_lru_headers(dns_rbtdb_t *rbtdb, unsigned int locknum, size_t purgesize,
|
||||||
* under an overmem condition. To recover from this condition quickly, up to
|
|
||||||
* 2 entries will be purged. This process is triggered while adding a new
|
|
||||||
* 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
|
|
||||||
* entries of the same name of different RR 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
|
|
||||||
overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, isc_stdtime_t now,
|
|
||||||
bool tree_locked) {
|
bool tree_locked) {
|
||||||
rdatasetheader_t *header, *header_prev;
|
rdatasetheader_t *header, *header_prev;
|
||||||
unsigned int locknum;
|
size_t purged = 0;
|
||||||
int purgecount = 2;
|
|
||||||
|
|
||||||
for (locknum = (locknum_start + 1) % rbtdb->node_lock_count;
|
|
||||||
locknum != locknum_start && purgecount > 0;
|
|
||||||
locknum = (locknum + 1) % rbtdb->node_lock_count)
|
|
||||||
{
|
|
||||||
NODE_LOCK(&rbtdb->node_locks[locknum].lock,
|
|
||||||
isc_rwlocktype_write);
|
|
||||||
|
|
||||||
header = isc_heap_element(rbtdb->heaps[locknum], 1);
|
|
||||||
if (header && header->rdh_ttl < now - RBTDB_VIRTUAL) {
|
|
||||||
expire_header(rbtdb, header, tree_locked, expire_ttl);
|
|
||||||
purgecount--;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (header = ISC_LIST_TAIL(rbtdb->rdatasets[locknum]);
|
for (header = ISC_LIST_TAIL(rbtdb->rdatasets[locknum]);
|
||||||
header != NULL && purgecount > 0; header = header_prev)
|
header != NULL && purged <= purgesize; header = header_prev)
|
||||||
{
|
{
|
||||||
header_prev = ISC_LIST_PREV(header, link);
|
header_prev = ISC_LIST_PREV(header, link);
|
||||||
/*
|
/*
|
||||||
@ -10163,12 +10157,42 @@ overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, isc_stdtime_t now,
|
|||||||
* referenced any more (so unlinking is safe) since the
|
* referenced any more (so unlinking is safe) since the
|
||||||
* TTL was reset to 0.
|
* TTL was reset to 0.
|
||||||
*/
|
*/
|
||||||
ISC_LIST_UNLINK(rbtdb->rdatasets[locknum], header,
|
ISC_LIST_UNLINK(rbtdb->rdatasets[locknum], header, link);
|
||||||
link);
|
size_t header_size = rdataset_size(header);
|
||||||
expire_header(rbtdb, header, tree_locked, expire_lru);
|
expire_header(rbtdb, header, tree_locked, expire_lru);
|
||||||
purgecount--;
|
purged += header_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (purged);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*%
|
||||||
|
* Purge some stale (i.e. unused for some period - LRU based cleaning) cache
|
||||||
|
* entries under the overmem condition. To recover from this condition quickly,
|
||||||
|
* we cleanup entries up to the size of newly added rdata (passed as purgesize).
|
||||||
|
*
|
||||||
|
* This process is triggered while adding a new 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 entries of the same name of different RR
|
||||||
|
* 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
|
||||||
|
overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, size_t purgesize,
|
||||||
|
bool tree_locked) {
|
||||||
|
unsigned int locknum;
|
||||||
|
size_t purged = 0;
|
||||||
|
|
||||||
|
for (locknum = (locknum_start + 1) % rbtdb->node_lock_count;
|
||||||
|
locknum != locknum_start && purged <= purgesize;
|
||||||
|
locknum = (locknum + 1) % rbtdb->node_lock_count)
|
||||||
|
{
|
||||||
|
NODE_LOCK(&rbtdb->node_locks[locknum].lock,
|
||||||
|
isc_rwlocktype_write);
|
||||||
|
|
||||||
|
purged += expire_lru_headers(rbtdb, locknum, purgesize - purged,
|
||||||
|
tree_locked);
|
||||||
|
|
||||||
NODE_UNLOCK(&rbtdb->node_locks[locknum].lock,
|
NODE_UNLOCK(&rbtdb->node_locks[locknum].lock,
|
||||||
isc_rwlocktype_write);
|
isc_rwlocktype_write);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user