]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Stale answer lookups could loop when over recursion quota
authorEvan Hunt <each@isc.org>
Fri, 26 May 2023 06:53:50 +0000 (23:53 -0700)
committerMichal Nowak <mnowak@isc.org>
Fri, 9 Jun 2023 13:52:51 +0000 (15:52 +0200)
When a query was aborted because of the recursion quota being exceeded,
but triggered a stale answer response and a stale data refresh query,
it could cause named to loop back where we are iterating and following
a delegation. Having no good answer in cache, we would fall back to
using serve-stale again, use the stale data, try to refresh the RRset,
and loop back again, without ever terminating until crashing due to
stack overflow.

This happens because in the functions 'query_notfound()' and
'query_delegation_recurse()', we check whether we can fall back to
serving stale data. We shouldn't do so if we are already refreshing
an RRset due to having prioritized stale data in cache.

In other words, we need to add an extra check to 'query_usestale()' to
disallow serving stale data if we are currently refreshing a stale
RRset.

As an additional mitigation to prevent looping, we now use the result
code ISC_R_ALREADYRUNNING rather than ISC_R_FAILURE when a recursion
loop is encountered, and we check for that condition in
'query_usestale()' as well.

lib/ns/query.c

index 1d7f70ae1cb7f1c27e30f32ed90a4a12988818b4..28bef2d99c9c01142c0adce28a5861dd8561b4f5 100644 (file)
@@ -6324,7 +6324,7 @@ ns_query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
        if (recparam_match(&client->query.recparam, qtype, qname, qdomain)) {
                ns_client_log(client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY,
                              ISC_LOG_INFO, "recursion loop detected");
-               return (ISC_R_FAILURE);
+               return (ISC_R_ALREADYRUNNING);
        }
 
        recparam_update(&client->query.recparam, qtype, qname, qdomain);
@@ -7288,10 +7288,21 @@ query_usestale(query_ctx_t *qctx, isc_result_t result) {
                return (false);
        }
 
-       if (result == DNS_R_DUPLICATE || result == DNS_R_DROP) {
+       if (qctx->refresh_rrset) {
+               /*
+                * This is a refreshing query, we have already prioritized
+                * stale data, so don't enable serve-stale again.
+                */
+               return (false);
+       }
+
+       if (result == DNS_R_DUPLICATE || result == DNS_R_DROP ||
+           result == ISC_R_ALREADYRUNNING)
+       {
                /*
                 * Don't enable serve-stale if the result signals a duplicate
-                * query or query that is being dropped.
+                * query or a query that is being dropped or can't proceed
+                * because of a recursion loop.
                 */
                return (false);
        }