summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJamal Hadi Salim <jhs@mojatatu.com>2026-09-12 14:09:19 -0400
committerPaolo Abeni <pabeni@redhat.com>2026-09-15 13:30:53 +0200
commit2cef2588c995722a901368def30befeef9ae55c6 (patch)
tree8c242c03aa2c6c5b827df2511ebc986b812cb43a
parent18a6fe05fb6e18de29fa90d388bb34044114b3d8 (diff)
downloadlinux-next-2cef2588c995722a901368def30befeef9ae55c6.tar.gz
linux-next-2cef2588c995722a901368def30befeef9ae55c6.zip
net/sched: hhf: cap hh_flows_limit at change time
hhf_change() stores TCA_HHF_HH_FLOWS_LIMIT with no upper bound. A huge hh_flows_limit lets each new heavy-hitter flow pass the hh_flows_current_cnt check in alloc_new_hh() and forces a fixed-size kzalloc(GFP_ATOMIC) per flow under spoofed traffic, for unbounded memory growth. Bound the attribute with NLA_POLICY_MAX() at 2*HH_FLOWS_CNT (the hhf_init() default) and report the rejected value via extack. The deprecated nested parse is kept: legacy tc does not set NLA_F_NESTED on TCA_OPTIONS. Configs relying on hh_limit above the default were relying on unbounded, unsafe behaviour and are not supported going forward. hhf_init() also ran hhf_change() before setting the default hh_flows_limit, so a user-supplied hh_limit at add time was clobbered back to 2048. Set the default before hhf_change() so the configured value sticks. This is a follow-up to commit eb56a495f59b ("net/sched: hhf: clamp quantum in change and init paths"), which bounded the quantum of the same qdisc; the hh_flows_limit bound is the remaining unbounded knob of that series' scope. Conditions to recreate the bug: CAP_NET_ADMIN in a user namespace; tc qdisc change dev X root hhf hh_limit 4294967295 succeeds and the value is echoed by tc qdisc show, unbounding heavy-hitter flow allocations; also tc qdisc add dev X root hhf hh_limit 500 stores 2048 instead of 500. Fixes: 10239edf86f1 ("net-qdisc-hhf: Heavy-Hitter Filter (HHF) qdisc") Cc: stable@vger.kernel.org Reported-by: Sashiko (gemini) <sashiko-bot@kernel.org> Closes: https://sashiko.dev/#/patchset/20260822195509.112717-1-jhs@mojatatu.com Reviewed-by: Victor Nogueira <victor@mojatatu.com> Tested-by: hybris <hybris@mojatatu.ai> Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com> Reviewed-by: Simon Horman <horms@kernel.org> Link: https://patch.msgid.link/QDISC-B855.v1.20260911153152@mojatatu.com Signed-off-by: Paolo Abeni <pabeni@redhat.com>
-rw-r--r--net/sched/sch_hhf.c9
1 files changed, 5 insertions, 4 deletions
diff --git a/net/sched/sch_hhf.c b/net/sched/sch_hhf.c
index fc72f825fbd9..5dec1ed969ad 100644
--- a/net/sched/sch_hhf.c
+++ b/net/sched/sch_hhf.c
@@ -527,7 +527,7 @@ static void hhf_destroy(struct Qdisc *sch)
static const struct nla_policy hhf_policy[TCA_HHF_MAX + 1] = {
[TCA_HHF_BACKLOG_LIMIT] = { .type = NLA_U32 },
[TCA_HHF_QUANTUM] = { .type = NLA_U32 },
- [TCA_HHF_HH_FLOWS_LIMIT] = { .type = NLA_U32 },
+ [TCA_HHF_HH_FLOWS_LIMIT] = NLA_POLICY_MAX(NLA_U32, 2 * HH_FLOWS_CNT),
[TCA_HHF_RESET_TIMEOUT] = { .type = NLA_U32 },
[TCA_HHF_ADMIT_BYTES] = { .type = NLA_U32 },
[TCA_HHF_EVICT_TIMEOUT] = { .type = NLA_U32 },
@@ -546,7 +546,7 @@ static int hhf_change(struct Qdisc *sch, struct nlattr *opt,
u32 new_hhf_non_hh_weight = q->hhf_non_hh_weight;
err = nla_parse_nested_deprecated(tb, TCA_HHF_MAX, opt, hhf_policy,
- NULL);
+ extack);
if (err < 0)
return err;
@@ -624,6 +624,9 @@ static int hhf_init(struct Qdisc *sch, struct nlattr *opt,
q->hhf_evict_timeout = HZ; /* 1 sec */
q->hhf_non_hh_weight = 2;
+ /* Cap max active HHs at twice len of hh_flows table. */
+ q->hh_flows_limit = 2 * HH_FLOWS_CNT;
+
if (opt) {
int err = hhf_change(sch, opt, extack);
@@ -639,8 +642,6 @@ static int hhf_init(struct Qdisc *sch, struct nlattr *opt,
for (i = 0; i < HH_FLOWS_CNT; i++)
INIT_LIST_HEAD(&q->hh_flows[i]);
- /* Cap max active HHs at twice len of hh_flows table. */
- q->hh_flows_limit = 2 * HH_FLOWS_CNT;
q->hh_flows_overlimit = 0;
q->hh_flows_total_cnt = 0;
q->hh_flows_current_cnt = 0;