From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 313BE49891A; Wed, 23 Sep 2026 14:21:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790173308; cv=none; b=ktx+OMIHl/XL1ejpfAfMW1W5Y7nuWwSE+XT73xszJBHx0fNlYKzEP7BW2DXimy4Ql24SDHHCbVpjKVqeTF/6MlyNPNaxXWnxfy8R0yG/7umOCcnpqWt7WgSSchTneL9wOxOUk6CBj1PkCuum5rHZ4P8e3wG1fLLSJ1dVUZPa1Os= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790173308; c=relaxed/simple; bh=gJqXIA5a852NijiDw+HSX1D4gz1MRphr/Bf8qySJbF4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=ruiPG3+5Btc+q0gUohG8GU2iLYPsVz3oS6/5uoYDw5LIAS+pOt9+Gcad+X8vXfYxKwHlRFs1C8O8Yed5O+vIIn/dl+qOBx1P2YCN+yX+vVbqLKFKKUrD1qlhQNS0Z0n03aRZDi92PTiTJBZnca2hEwrxDQAYSkhkcFhOI2smhes= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b=kiouhf+p; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b="kiouhf+p" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 7CB1C1F000FF; Wed, 23 Sep 2026 14:21:46 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linuxfoundation.org; s=korg; t=1790173307; bh=wkmLFX822hb9T3M5ARrc8NG6S8XjBjK7mCcO91a+W6g=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=kiouhf+p7cdkklpYVIs++uKJo3MIRXwmTw/DkBF/8VzFgZo4QDzV2wLa4jT5OdG7u tB0176yyORAhHhnPqYHkDwnTT5/QYmuB9dUKt1t7cgBnZhw1gIZhbhOtkxTuxFrRBX nyCsrp16EYOvyxoPd5rn8Se2gjsNlxN1dxWgnscQ= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, Vega , Eric Dumazet , Victor Nogueira , Jamal Hadi Salim , =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= , Jakub Kicinski , Sasha Levin Subject: [PATCH 7.2 205/438] net/sched: codel: bound the dropping loop per dequeue call Date: Wed, 23 Sep 2026 16:03:46 +0200 Message-ID: <20260923140650.076429405@linuxfoundation.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260923140644.756254324@linuxfoundation.org> References: <20260923140644.756254324@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore Precedence: bulk X-Mailing-List: patches@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 7.2-stable review patch. If anyone has any objections, please let me know. ------------------ From: Jamal Hadi Salim [ Upstream commit 7f4a5ec6258fd7c92633ec4b0493fc51166d9398 ] The CoDel control law schedules the next drop one interval/sqrt(count) after the previous drop, using the configured interval (codel_params.interval). For very small intervals the scheduled step rounds down to zero, so the dropping loop in codel_dequeue() never advances and drains the entire backlog under the qdisc lock in one call - an unprivileged user can trigger a soft lockup this way. Fix in the shared codel code used by both codel and fq_codel: 1. Make the control-law step at least 1 tick so the dropping loop always moves forward. 2. Cap the dropping loop at CODEL_MAX_DROPS_PER_DEQUEUE (256) drops per codel_dequeue() call, resyncing drop_next to now when the cap is hit: the catch-up owed to the loop grows with the idle gap and the backlog, which no interval threshold can bound. This is a deliberate behaviour change after long idle gaps. The cap applies to fq_codel (4b549a2ef4be) and the mac80211 TXQ path (fixed interval, cap only). The target sojourn delay (codel_params.target) is not validated: it does not feed the control law, so a sub-tick value is aggressive rather than deadlock-prone. Conditions to recreate the bug: - tc qdisc add dev lo root handle 1: tbf rate 1kbit burst 2kb limit 1000000 - tc qdisc add dev lo parent 1:1 handle 10: codel interval 2us target 1ms noecn limit 1000000 (same for fq_codel) - unpatched kernel: tc accepts it; a UDP flood under the 1kbit tbf soft-lockups (watchdog: BUG: soft lockup) while one codel_dequeue() call drops the backlog under the qdisc lock - patched kernel: same setup, at most 256 drops per dequeue call, no soft lockup Testing: claim reproducer and interval 2us/3us variants run clean; tdc qdisc category passes (see the selftests patch). Fixes: 76e3cc126bb2 ("codel: Controlled Delay AQM") Reported-by: Vega Reviewed-by: Eric Dumazet Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Reviewed-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/QDISC-1L5H.v1.20260912080102@mojatatu.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- include/net/codel.h | 5 +++++ include/net/codel_impl.h | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/include/net/codel.h b/include/net/codel.h index aa80f744826cd..183d43c2bd434 100644 --- a/include/net/codel.h +++ b/include/net/codel.h @@ -140,6 +140,11 @@ struct codel_vars { /* needed shift to get a Q0.32 number from rec_inv_sqrt */ #define REC_INV_SQRT_SHIFT (32 - REC_INV_SQRT_BITS) +/* Cap on drops per codel_dequeue() call: the loop's work depends on the + * idle gap and backlog, both outside our control; resync when exceeded. + */ +#define CODEL_MAX_DROPS_PER_DEQUEUE 256 + /** * struct codel_stats - contains codel shared variables and stats * @maxpacket: largest packet we've seen so far diff --git a/include/net/codel_impl.h b/include/net/codel_impl.h index 2c1f0ec309e9f..8f26132d45b7f 100644 --- a/include/net/codel_impl.h +++ b/include/net/codel_impl.h @@ -93,12 +93,17 @@ static void codel_Newton_step(struct codel_vars *vars) * CoDel control_law is t + interval/sqrt(count) * We maintain in rec_inv_sqrt the reciprocal value of sqrt(count) to avoid * both sqrt() and divide operation. + * + * Clamp the increment to at least 1 tick: a very small interval (or a + * large count) can truncate it to zero, stalling the dropping loop. */ static codel_time_t codel_control_law(codel_time_t t, codel_time_t interval, u32 rec_inv_sqrt) { - return t + reciprocal_scale(interval, rec_inv_sqrt << REC_INV_SQRT_SHIFT); + return t + max_t(u32, 1, + reciprocal_scale(interval, + rec_inv_sqrt << REC_INV_SQRT_SHIFT)); } static bool codel_should_drop(const struct sk_buff *skb, @@ -154,6 +159,7 @@ static struct sk_buff *codel_dequeue(void *ctx, codel_skb_dequeue_t dequeue_func) { struct sk_buff *skb = dequeue_func(vars, ctx); + unsigned int drops = 0; codel_time_t now; bool drop; @@ -180,6 +186,14 @@ static struct sk_buff *codel_dequeue(void *ctx, */ while (vars->dropping && codel_time_after_eq(now, vars->drop_next)) { + if (++drops > CODEL_MAX_DROPS_PER_DEQUEUE) { + /* fell far behind the schedule */ + WRITE_ONCE(vars->drop_next, + codel_control_law(now, + params->interval, + vars->rec_inv_sqrt)); + break; + } /* dont care of possible wrap * since there is no more divide. */ -- 2.53.0