[PATCH] net/sched: cls_u32: pass classifier flags to u32_replace_hw_knode()

From: Noah Weinberger

Date: Sun Nov 08 2026 - 18:52:09 EST


Hello,

The flags word cls_api passes to tp->ops->change() is in the
TCA_ACT_FLAGS namespace (REPLACE, NO_RTNL, AT_INGRESS...); the
classifier flags (SKIP_HW / SKIP_SW) arrive separately as
TCA_U32_FLAGS and are stored in n->flags. u32_change() forwards the
action word to u32_replace_hw_knode(), so inside that function

bool skip_sw = tc_skip_sw(flags);
...
if (skip_sw && !(n->flags & TCA_CLS_FLAGS_IN_HW))
return -EINVAL;

can never trigger: skip_sw is always false. As a result
TCA_CLS_FLAGS_SKIP_HW is ignored for knodes (they are always offered
to the driver, unlike flower, which checks tc_skip_hw), and a filter
installed with SKIP_SW on a device without u32 offload is accepted
and then never matches in software - a silently blackholed filter
that the dead guard was supposed to reject.

The patch passes the classifier flags (userflags) on both call
sites, mirroring what u32_replace_hw_hnode() already does. It also
un-masks the related hardware-failure error paths (previously a
no-driver environment could never reach them).

Affected version/commit: mainline 8a65162514fbad21a2c58ffed65f42b27e92b163 (v7.4-rc2 window, 2026-11-08), net/sched/cls_u32.c:550-587 (u32_replace_hw_knode computes skip_sw = tc_skip_sw(flags) and gates on skip_sw && !IN_HW), :954 and :1200 (called with the cls_api action-flags word), :1028 (u32_replace_hw_hnode correctly uses userflags), net/sched/cls_api.c:~2460-2468 (the flags word passed to tp->ops->change is built from TCA_ACT_FLAGS_REPLACE/NO_RTNL/AT_INGRESS, not TCA_CLS_FLAGS_*)
The action/classifier flags split predates the reviewed window.

This was found during an assisted (AI-supported) static source review of
mainline 8a65162514fbad21a2c58ffed65f42b27e92b163 (v7.4-rc2 window, 2026-11-08). The patch below has NOT been compiled and has
NOT been runtime tested, and no reproducer was produced - the triggering
conditions are described above instead. Please treat this as a report for
verification rather than a battle-tested fix.

---
diff --git a/net/sched/cls_u32.c b/net/sched/cls_u32.c
index 91f4e64587..bdb131b172 100644
--- a/net/sched/cls_u32.c
+++ b/net/sched/cls_u32.c
@@ -951,7 +951,7 @@ static int u32_change(struct net *net, struct sk_buff *in_skb,

u32_bind_filter(tp, new, base, tb);

- err = u32_replace_hw_knode(tp, new, flags, extack);
+ err = u32_replace_hw_knode(tp, new, userflags, extack);
if (err) {
u32_unbind_filter(tp, new, tb);

@@ -1197,7 +1197,7 @@ static int u32_change(struct net *net, struct sk_buff *in_skb,
struct tc_u_knode __rcu **ins;
struct tc_u_knode *pins;

- err = u32_replace_hw_knode(tp, n, flags, extack);
+ err = u32_replace_hw_knode(tp, n, userflags, extack);
if (err)
goto errunbind;