diff --git a/changes/bug18642 b/changes/bug18642 new file mode 100644 index 0000000000..6b71f3c6dd --- /dev/null +++ b/changes/bug18642 @@ -0,0 +1,5 @@ + o Minor features (denial-of-service avoidance): + - Make our OOM handler aware of the DNS cache so that it doesn't fill up + the memory. This check is important for our DoS mitigation subsystem. + Closes ticket 18642. Patch by Neel Chauhan + diff --git a/src/core/or/relay.c b/src/core/or/relay.c index 51084e2a17..6e1adfaff3 100644 --- a/src/core/or/relay.c +++ b/src/core/or/relay.c @@ -64,6 +64,7 @@ #include "lib/crypt_ops/crypto_rand.h" #include "lib/crypt_ops/crypto_util.h" #include "feature/dircache/directory.h" +#include "feature/relay/dns.h" #include "feature/stats/geoip.h" #include "feature/hs/hs_cache.h" #include "core/mainloop/main.h" @@ -2538,6 +2539,8 @@ cell_queues_check_size(void) const size_t geoip_client_cache_total = geoip_client_cache_total_allocation(); alloc += geoip_client_cache_total; + const size_t dns_cache_total = dns_cache_total_allocation(); + alloc += dns_cache_total; if (alloc >= get_options()->MaxMemInQueues_low_threshold) { last_time_under_memory_pressure = approx_time(); if (alloc >= get_options()->MaxMemInQueues) { @@ -2555,6 +2558,11 @@ cell_queues_check_size(void) (size_t)(get_options()->MaxMemInQueues / 10); alloc -= geoip_client_cache_handle_oom(now, bytes_to_remove); } + if (dns_cache_total > get_options()->MaxMemInQueues / 5) { + const size_t bytes_to_remove = + dns_cache_total - (size_t)(get_options()->MaxMemInQueues / 10); + alloc -= dns_cache_handle_oom(now, bytes_to_remove); + } circuits_handle_oom(alloc); return 1; } diff --git a/src/feature/relay/dns.c b/src/feature/relay/dns.c index f4e39dfd3d..48319b47c8 100644 --- a/src/feature/relay/dns.c +++ b/src/feature/relay/dns.c @@ -2076,14 +2076,21 @@ dns_cache_entry_count(void) return HT_SIZE(&cache_root); } +/* Return the total size in bytes of the DNS cache. */ +size_t +dns_cache_total_allocation(void) +{ + return sizeof(struct cached_resolve_t) * dns_cache_entry_count() + + HT_MEM_USAGE(&cache_root); +} + /** Log memory information about our internal DNS cache at level 'severity'. */ void dump_dns_mem_usage(int severity) { /* This should never be larger than INT_MAX. */ int hash_count = dns_cache_entry_count(); - size_t hash_mem = sizeof(struct cached_resolve_t) * hash_count; - hash_mem += HT_MEM_USAGE(&cache_root); + size_t hash_mem = dns_cache_total_allocation(); /* Print out the count and estimated size of our &cache_root. It undercounts hostnames in cached reverse resolves. @@ -2093,6 +2100,36 @@ dump_dns_mem_usage(int severity) (unsigned)hash_mem); } +/* Do a round of OOM cleanup on all DNS entries. Return the amount of removed + * bytes. It is possible that the returned value is lower than min_remove_bytes + * if the caches get emptied out so the caller should be aware of this. */ +size_t +dns_cache_handle_oom(time_t now, size_t min_remove_bytes) +{ + time_t time_inc = 0; + size_t total_bytes_removed = 0; + size_t current_size = dns_cache_total_allocation(); + + do { + /* If no DNS entries left, break loop. */ + if (!dns_cache_entry_count()) + break; + + /* Get cutoff interval and remove entries. */ + time_t cutoff = now + time_inc; + purge_expired_resolves(cutoff); + + /* Update amount of bytes removed and array size. */ + size_t bytes_removed = current_size - dns_cache_total_allocation(); + current_size -= bytes_removed; + total_bytes_removed += bytes_removed; + + time_inc += 3600; /* Increase time_inc by 1 hour. */ + } while (total_bytes_removed < min_remove_bytes); + + return total_bytes_removed; +} + #ifdef DEBUG_DNS_CACHE /** Exit with an assertion if the DNS cache is corrupt. */ static void diff --git a/src/feature/relay/dns.h b/src/feature/relay/dns.h index 69c1764b73..1dd6f903d1 100644 --- a/src/feature/relay/dns.h +++ b/src/feature/relay/dns.h @@ -38,7 +38,9 @@ void dns_launch_correctness_checks(void); int dns_seems_to_be_broken(void); int dns_seems_to_be_broken_for_ipv6(void); void dns_reset_correctness_checks(void); +size_t dns_cache_total_allocation(void); void dump_dns_mem_usage(int severity); +size_t dns_cache_handle_oom(time_t now, size_t min_remove_bytes); #ifdef DNS_PRIVATE #include "feature/relay/dns_structs.h"