From: Willy Tarreau Date: Thu, 6 Aug 2026 07:31:15 +0000 (+0200) Subject: BUG/MINOR: lb-chash: bound the walk when the saved cursor changed tree X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=23cf528c76cfbea9dbdf89cd9b885e0398884934;p=thirdparty%2Fhaproxy.git BUG/MINOR: lb-chash: bound the walk when the saved cursor changed tree chash.last is not reset when the tree in use switches from the backup servers back to the active ones. The next call then starts walking the active tree while keeping a backup node as the stop condition, which it can never reach, so it cycles over the active tree forever as soon as no server can be picked: all of them saturated (maxconn reached with a queue, or served >= the dynamic maxconn), or the only one left being the server to avoid on a redispatch. In this case, if chash.last (assigned to ) isn't in the current tree, the loop will run forever. Let's just count the number of times we wrap and stop at the second, which indicates that the stop server is not in the tree. Note that this is more theoretical than practical: this needs "hash-type consistent" with requests not carrying the hash key so that this fallback is used at all, backup servers with "option allbackups" (without it lbprm.fbck is returned directly and the backup tree is never walked), and all active servers going down then one coming back while the remaining ones are saturated. In practice it has been there since consistent hash was introduced in 1.4 by commit 6b2e11be1 ("[MEDIUM] backend: implement consistent hashing variation") and was never reported. It may be backported to all stable versions. Reported-by: Claude (ANT-2026-98TCHHRD) --- diff --git a/src/lb_chash.c b/src/lb_chash.c index a92ba7cef..12a933a21 100644 --- a/src/lb_chash.c +++ b/src/lb_chash.c @@ -501,6 +501,7 @@ struct server *chash_get_next_server(struct proxy *p, struct server *srvtoavoid) struct server *srv, *avoided; struct eb32_node *node, *stop, *avoided_node; struct eb_root *root; + int wrapped = 0; srv = avoided = NULL; avoided_node = NULL; @@ -525,8 +526,16 @@ struct server *chash_get_next_server(struct proxy *p, struct server *srvtoavoid) if (node) node = eb32_next(node); - if (!node) + if (!node) { + /* Reaching the end of the tree twice means that + * does not belong to (e.g. active servers found + * from .last when only backup usable). So let's count + * wraps to avoid looping forever. + */ + if (wrapped++) + break; node = eb32_first(root); + } p->lbprm.chash.last = node; if (!node) {