/
githubmirror
/
c-ares
Обзор
Документация
Войти
/
githubmirror
/
c-ares
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/lib/ares_qcache.c
442 строки
12 KB
David Hotham
Fix memory leak of qcache entry on key allocation failure (#1127)
12 апр 2026, 15:53
Не верифицирован
12 апр 2026, 15:53
299bd61
Код
Авторство
О чём код?
/* MIT License * * Copyright (c) 2023 Brad House * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * SPDX-License-Identifier: MIT */ #include "ares_private.h" struct ares_qcache { ares_htable_strvp_t *cache; ares_slist_t *expire; unsigned int max_ttl; }; typedef struct { char *key; ares_dns_record_t *dnsrec; time_t expire_ts; time_t insert_ts; } ares_qcache_entry_t; static char *ares_qcache_calc_key(const ares_dns_record_t *dnsrec) { ares_buf_t *buf = ares_buf_create(); size_t i; ares_status_t status; ares_dns_flags_t flags; if (dnsrec == NULL || buf == NULL) { return NULL; /* LCOV_EXCL_LINE: DefensiveCoding */ } /* Format is OPCODE|FLAGS[|QTYPE1|QCLASS1|QNAME1]... */ status = ares_buf_append_str( buf, ares_dns_opcode_tostr(ares_dns_record_get_opcode(dnsrec))); if (status != ARES_SUCCESS) { goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ } status = ares_buf_append_byte(buf, '|'); if (status != ARES_SUCCESS) { goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ } flags = ares_dns_record_get_flags(dnsrec); /* Only care about RD and CD */ if (flags & ARES_FLAG_RD) { status = ares_buf_append_str(buf, "rd"); if (status != ARES_SUCCESS) { goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ } } if (flags & ARES_FLAG_CD) { status = ares_buf_append_str(buf, "cd"); if (status != ARES_SUCCESS) { goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ } } for (i = 0; i < ares_dns_record_query_cnt(dnsrec); i++) { const char *name; size_t name_len; ares_dns_rec_type_t qtype; ares_dns_class_t qclass; status = ares_dns_record_query_get(dnsrec, i, &name, &qtype, &qclass); if (status != ARES_SUCCESS) { goto fail; /* LCOV_EXCL_LINE: DefensiveCoding */ } status = ares_buf_append_byte(buf, '|'); if (status != ARES_SUCCESS) { goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ } status = ares_buf_append_str(buf, ares_dns_rec_type_tostr(qtype)); if (status != ARES_SUCCESS) { goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ } status = ares_buf_append_byte(buf, '|'); if (status != ARES_SUCCESS) { goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ } status = ares_buf_append_str(buf, ares_dns_class_tostr(qclass)); if (status != ARES_SUCCESS) { goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ } status = ares_buf_append_byte(buf, '|'); if (status != ARES_SUCCESS) { goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ } /* On queries, a '.' may be appended to the name to indicate an explicit * name lookup without performing a search. Strip this since its not part * of a cached response. */ name_len = ares_strlen(name); if (name_len && name[name_len - 1] == '.') { name_len--; } if (name_len > 0) { status = ares_buf_append(buf, (const unsigned char *)name, name_len); if (status != ARES_SUCCESS) { goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ } } } return ares_buf_finish_str(buf, NULL); /* LCOV_EXCL_START: OutOfMemory */ fail: ares_buf_destroy(buf); return NULL; /* LCOV_EXCL_STOP */ } static void ares_qcache_expire(ares_qcache_t *cache, const ares_timeval_t *now) { ares_slist_node_t *node; if (cache == NULL) { return; } while ((node = ares_slist_node_first(cache->expire)) != NULL) { const ares_qcache_entry_t *entry = ares_slist_node_val(node); /* If now is NULL, we're flushing everything, so don't break */ if (now != NULL && entry->expire_ts > now->sec) { break; } ares_htable_strvp_remove(cache->cache, entry->key); ares_slist_node_destroy(node); } } void ares_qcache_flush(ares_qcache_t *cache) { ares_qcache_expire(cache, NULL /* flush all */); } void ares_qcache_destroy(ares_qcache_t *cache) { if (cache == NULL) { return; } ares_htable_strvp_destroy(cache->cache); ares_slist_destroy(cache->expire); ares_free(cache); } static int ares_qcache_entry_sort_cb(const void *arg1, const void *arg2) { const ares_qcache_entry_t *entry1 = arg1; const ares_qcache_entry_t *entry2 = arg2; if (entry1->expire_ts > entry2->expire_ts) { return 1; } if (entry1->expire_ts < entry2->expire_ts) { return -1; } return 0; } static void ares_qcache_entry_destroy_cb(void *arg) { ares_qcache_entry_t *entry = arg; if (entry == NULL) { return; /* LCOV_EXCL_LINE: DefensiveCoding */ } ares_free(entry->key); ares_dns_record_destroy(entry->dnsrec); ares_free(entry); } ares_status_t ares_qcache_create(ares_rand_state *rand_state, unsigned int max_ttl, ares_qcache_t **cache_out) { ares_status_t status = ARES_SUCCESS; ares_qcache_t *cache; cache = ares_malloc_zero(sizeof(*cache)); if (cache == NULL) { status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ goto done; /* LCOV_EXCL_LINE: OutOfMemory */ } cache->cache = ares_htable_strvp_create(NULL); if (cache->cache == NULL) { status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ goto done; /* LCOV_EXCL_LINE: OutOfMemory */ } cache->expire = ares_slist_create(rand_state, ares_qcache_entry_sort_cb, ares_qcache_entry_destroy_cb); if (cache->expire == NULL) { status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ goto done; /* LCOV_EXCL_LINE: OutOfMemory */ } cache->max_ttl = max_ttl; done: if (status != ARES_SUCCESS) { *cache_out = NULL; ares_qcache_destroy(cache); return status; } *cache_out = cache; return status; } static unsigned int ares_qcache_calc_minttl(ares_dns_record_t *dnsrec) { unsigned int minttl = 0xFFFFFFFF; size_t sect; for (sect = ARES_SECTION_ANSWER; sect <= ARES_SECTION_ADDITIONAL; sect++) { size_t i; for (i = 0; i < ares_dns_record_rr_cnt(dnsrec, (ares_dns_section_t)sect); i++) { const ares_dns_rr_t *rr = ares_dns_record_rr_get(dnsrec, (ares_dns_section_t)sect, i); ares_dns_rec_type_t type = ares_dns_rr_get_type(rr); unsigned int ttl = ares_dns_rr_get_ttl(rr); /* TTL is meaningless on these record types */ if (type == ARES_REC_TYPE_OPT || type == ARES_REC_TYPE_SOA || type == ARES_REC_TYPE_SIG) { continue; } if (ttl < minttl) { minttl = ttl; } } } return minttl; } static unsigned int ares_qcache_soa_minimum(ares_dns_record_t *dnsrec) { size_t i; /* RFC 2308 Section 5 says its the minimum of MINIMUM and the TTL of the * record. */ for (i = 0; i < ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_AUTHORITY); i++) { const ares_dns_rr_t *rr = ares_dns_record_rr_get(dnsrec, ARES_SECTION_AUTHORITY, i); ares_dns_rec_type_t type = ares_dns_rr_get_type(rr); unsigned int ttl; unsigned int minimum; if (type != ARES_REC_TYPE_SOA) { continue; } minimum = ares_dns_rr_get_u32(rr, ARES_RR_SOA_MINIMUM); ttl = ares_dns_rr_get_ttl(rr); if (ttl > minimum) { return minimum; } return ttl; } return 0; } /* On success, takes ownership of dnsrec */ static ares_status_t ares_qcache_insert_int(ares_qcache_t *qcache, ares_dns_record_t *qresp, const ares_dns_record_t *qreq, const ares_timeval_t *now) { ares_qcache_entry_t *entry; unsigned int ttl; ares_dns_rcode_t rcode = ares_dns_record_get_rcode(qresp); ares_dns_flags_t flags = ares_dns_record_get_flags(qresp); if (qcache == NULL || qresp == NULL) { return ARES_EFORMERR; } /* Only save NOERROR or NXDOMAIN */ if (rcode != ARES_RCODE_NOERROR && rcode != ARES_RCODE_NXDOMAIN) { return ARES_ENOTIMP; } /* Don't save truncated queries */ if (flags & ARES_FLAG_TC) { return ARES_ENOTIMP; } /* Look at SOA for NXDOMAIN for minimum */ if (rcode == ARES_RCODE_NXDOMAIN) { ttl = ares_qcache_soa_minimum(qresp); } else { ttl = ares_qcache_calc_minttl(qresp); } if (ttl > qcache->max_ttl) { ttl = qcache->max_ttl; } /* Don't cache something that is already expired */ if (ttl == 0) { return ARES_EREFUSED; } entry = ares_malloc_zero(sizeof(*entry)); if (entry == NULL) { goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ } entry->dnsrec = qresp; entry->expire_ts = (time_t)now->sec + (time_t)ttl; entry->insert_ts = (time_t)now->sec; /* We can't guarantee the server responded with the same flags as the * request had, so we have to re-parse the request in order to generate the * key for caching, but we'll only do this once we know for sure we really * want to cache it */ entry->key = ares_qcache_calc_key(qreq); if (entry->key == NULL) { goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ } if (!ares_htable_strvp_insert(qcache->cache, entry->key, entry)) { goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ } if (ares_slist_insert(qcache->expire, entry) == NULL) { goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ } return ARES_SUCCESS; /* LCOV_EXCL_START: OutOfMemory */ fail: if (entry != NULL) { if (entry->key != NULL) { ares_htable_strvp_remove(qcache->cache, entry->key); ares_free(entry->key); } ares_free(entry); } return ARES_ENOMEM; /* LCOV_EXCL_STOP */ } ares_status_t ares_qcache_fetch(ares_channel_t *channel, const ares_timeval_t *now, const ares_dns_record_t *dnsrec, const ares_dns_record_t **dnsrec_resp) { char *key = NULL; ares_qcache_entry_t *entry; ares_status_t status = ARES_SUCCESS; if (channel == NULL || dnsrec == NULL || dnsrec_resp == NULL) { return ARES_EFORMERR; } if (channel->qcache == NULL) { return ARES_ENOTFOUND; } ares_qcache_expire(channel->qcache, now); key = ares_qcache_calc_key(dnsrec); if (key == NULL) { status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ goto done; /* LCOV_EXCL_LINE: OutOfMemory */ } entry = ares_htable_strvp_get_direct(channel->qcache->cache, key); if (entry == NULL) { status = ARES_ENOTFOUND; goto done; } ares_dns_record_ttl_decrement(entry->dnsrec, (unsigned int)(now->sec - entry->insert_ts)); *dnsrec_resp = entry->dnsrec; done: ares_free(key); return status; } ares_status_t ares_qcache_insert(ares_channel_t *channel, const ares_timeval_t *now, const ares_query_t *query, const ares_dns_record_t *dnsrec) { ares_dns_record_t *dupdns = ares_dns_record_duplicate(dnsrec); ares_status_t status; if (dupdns == NULL) { return ARES_ENOMEM; } status = ares_qcache_insert_int(channel->qcache, dupdns, query->query, now); if (status != ARES_SUCCESS) { ares_dns_record_destroy(dupdns); } return status; }