* [PATCH v1] ice: Lock TC flower filter list operations
@ 2026-10-20 3:32 Binbin Deng
2026-10-22 22:34 ` netdev-bot+sashiko
0 siblings, 1 reply; 2+ messages in thread
From: Binbin Deng @ 2026-10-20 3:32 UTC (permalink / raw)
To: anthony.l.nguyen, przemyslaw.kitszel, andrew+netdev, davem,
edumazet, kuba, pabeni
Cc: netdev, linux-kernel, Binbin Deng
The per-PF TC flower filter list is used without a common lock. TC flower
add and delete paths traverse the list, send firmware commands, and then
insert or remove and free nodes. The reset path can concurrently walk the
same list in ice_rem_all_chnl_fltrs() while removing queue-channel filters,
and ice_replay_tc_fltrs() replays the same list after reset. The reset
state check in the flower handlers is not a synchronization primitive.
The race is reachable by creating or deleting a TC flower filter while
service work handles a reset and removes queue channels. A flower
operation can obtain a filter node before reset teardown removes and frees
it, or reset/replay can traverse a list concurrently modified by flower
control operations.
This can produce a KASAN use-after-free, hlist corruption, stale filter
state after replay, or a panic. Firmware commands are slow-path
operations, so mutual exclusion is appropriate here.
Add a per-PF mutex and hold it across each list lookup, firmware operation,
list update, and free. This keeps every reader of the filter list stable
while an entry is being added, removed, or replayed.
Found during a static-analysis-assisted audit: candidate sites were
surfaced by a Coccinelle rule, and the reader/writer paths were
confirmed by LLM-assisted (GLM-5.3) code inspection. Due to a lack
of hardware infrastructureThe issue was not observed at runtime.
It is identified by code inspection only.
Fixes: 9fea749856d1 ("ice: Add tc-flower filter support for channel")
Assisted-by: GLM-5.3 coccinelle
Signed-off-by: Binbin Deng <18983559317@163.com>
---
drivers/net/ethernet/intel/ice/ice.h | 1 +
drivers/net/ethernet/intel/ice/ice_main.c | 3 +++
drivers/net/ethernet/intel/ice/ice_tc_lib.c | 26 +++++++++++++++++----
3 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h
index db3c7015c56c..dac14c97bcbc 100644
--- a/drivers/net/ethernet/intel/ice/ice.h
+++ b/drivers/net/ethernet/intel/ice/ice.h
@@ -643,6 +643,7 @@ struct ice_pf {
*/
u16 num_dmac_chnl_fltrs;
struct hlist_head tc_flower_fltr_list;
+ struct mutex tc_flower_fltr_lock; /* Protect TC flower filters */
u64 supported_rxdids;
diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
index d88835482d3a..90031ba17b06 100644
--- a/drivers/net/ethernet/intel/ice/ice_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_main.c
@@ -3877,6 +3877,7 @@ void ice_deinit_pf(struct ice_pf *pf)
mutex_destroy(&pf->adev_mutex);
mutex_destroy(&pf->sw_mutex);
mutex_destroy(&pf->tc_mutex);
+ mutex_destroy(&pf->tc_flower_fltr_lock);
mutex_destroy(&pf->avail_q_mutex);
mutex_destroy(&pf->vfs.table_lock);
@@ -3976,6 +3977,7 @@ int ice_init_pf(struct ice_pf *pf)
mutex_init(&pf->sw_mutex);
mutex_init(&pf->tc_mutex);
+ mutex_init(&pf->tc_flower_fltr_lock);
mutex_init(&pf->adev_mutex);
mutex_init(&pf->lag_mutex);
@@ -8882,6 +8884,7 @@ static void ice_rem_all_chnl_fltrs(struct ice_pf *pf)
hlist_del(&fltr->tc_flower_node);
kfree(fltr);
}
+ mutex_lock(&pf->tc_flower_fltr_lock);
}
/**
diff --git a/drivers/net/ethernet/intel/ice/ice_tc_lib.c b/drivers/net/ethernet/intel/ice/ice_tc_lib.c
index d20357c04127..955ad3cc5417 100644
--- a/drivers/net/ethernet/intel/ice/ice_tc_lib.c
+++ b/drivers/net/ethernet/intel/ice/ice_tc_lib.c
@@ -2276,21 +2276,29 @@ int ice_add_cls_flower(struct net_device *netdev, struct ice_vsi *vsi,
return -EINVAL;
}
+ mutex_lock(&pf->tc_flower_fltr_lock);
+
/* avoid duplicate entries, if exists - return error */
fltr = ice_find_tc_flower_fltr(pf, cls_flower->cookie);
if (fltr) {
NL_SET_ERR_MSG_MOD(extack, "filter cookie already exists, ignoring");
+ mutex_unlock(&pf->tc_flower_fltr_lock);
return -EEXIST;
}
/* prep and add TC-flower filter in HW */
err = ice_add_tc_fltr(netdev, vsi, cls_flower, &fltr, ingress);
if (err)
- return err;
+ goto unlock;
/* add filter into an ordered list */
hlist_add_head(&fltr->tc_flower_node, &pf->tc_flower_fltr_list);
+ mutex_unlock(&pf->tc_flower_fltr_lock);
return 0;
+
+unlock:
+ mutex_unlock(&pf->tc_flower_fltr_lock);
+ return err;
}
/**
@@ -2305,28 +2313,36 @@ ice_del_cls_flower(struct ice_vsi *vsi, struct flow_cls_offload *cls_flower)
struct ice_pf *pf = vsi->back;
int err;
+ mutex_lock(&pf->tc_flower_fltr_lock);
+
/* find filter */
fltr = ice_find_tc_flower_fltr(pf, cls_flower->cookie);
if (!fltr) {
if (!test_bit(ICE_FLAG_TC_MQPRIO, pf->flags) &&
- hlist_empty(&pf->tc_flower_fltr_list))
- return 0;
+ hlist_empty(&pf->tc_flower_fltr_list)) {
+ mutex_unlock(&pf->tc_flower_fltr_lock);
+ return 0;
+ }
NL_SET_ERR_MSG_MOD(cls_flower->common.extack, "failed to delete TC flower filter because unable to find it");
+ mutex_unlock(&pf->tc_flower_fltr_lock);
return -EINVAL;
}
fltr->extack = cls_flower->common.extack;
/* delete filter from HW */
err = ice_del_tc_fltr(vsi, fltr);
- if (err)
+ if (err) {
+ mutex_unlock(&pf->tc_flower_fltr_lock);
return err;
+ }
/* delete filter from an ordered list */
hlist_del(&fltr->tc_flower_node);
/* free the filter node */
kfree(fltr);
+ mutex_unlock(&pf->tc_flower_fltr_lock);
return 0;
}
@@ -2340,10 +2356,12 @@ void ice_replay_tc_fltrs(struct ice_pf *pf)
struct ice_tc_flower_fltr *fltr;
struct hlist_node *node;
+ mutex_lock(&pf->tc_flower_fltr_lock);
hlist_for_each_entry_safe(fltr, node,
&pf->tc_flower_fltr_list,
tc_flower_node) {
fltr->extack = NULL;
ice_add_switch_fltr(fltr->src_vsi, fltr);
}
+ mutex_unlock(&pf->tc_flower_fltr_lock);
}
--
2.43.0
^ permalink raw reply [flat|nested] 2+ messages in thread
* Re: [PATCH v1] ice: Lock TC flower filter list operations
2026-10-20 3:32 [PATCH v1] ice: Lock TC flower filter list operations Binbin Deng
@ 2026-10-22 22:34 ` netdev-bot+sashiko
0 siblings, 0 replies; 2+ messages in thread
From: netdev-bot+sashiko @ 2026-10-22 22:34 UTC (permalink / raw)
To: 18983559317
Cc: anthony.l.nguyen, przemyslaw.kitszel, andrew+netdev, davem,
edumazet, kuba, pabeni, netdev, linux-kernel
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 0 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] The patch puts the new lock in the wrong place in
ice_rem_all_chnl_fltrs() (drivers/net/ethernet/intel/ice/ice_main.c).
- [Low] The commit message describes ice_replay_tc_fltrs() as an active
reader that "replays the same list after reset", and lists "stale…
--
ice: Lock TC flower filter list operations
This commit adds a per-PF mutex, tc_flower_fltr_lock, to serialize access
to pf->tc_flower_fltr_list. The mutex is taken in ice_add_cls_flower(),
ice_del_cls_flower() and ice_replay_tc_fltrs(). A lock call is also added
to ice_rem_all_chnl_fltrs() on the queue-channel teardown path.
> diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
> index d88835482d3a..90031ba17b06 100644
> --- a/drivers/net/ethernet/intel/ice/ice_main.c
> +++ b/drivers/net/ethernet/intel/ice/ice_main.c
[ ... ]
> @@ -8882,6 +8884,7 @@ static void ice_rem_all_chnl_fltrs(struct ice_pf *pf)
> hlist_del(&fltr->tc_flower_node);
> kfree(fltr);
> }
> + mutex_lock(&pf->tc_flower_fltr_lock);
> }
[Severity: High]
Was this meant to be a mutex_lock() before the hlist_for_each_entry_safe()
loop, with a matching mutex_unlock() after it?
As written, the lock is taken only after the walk has finished, and nothing
releases it. So ice_rem_all_chnl_fltrs() always returns with
tc_flower_fltr_lock held, even when the list is empty.
ice_remove_q_channels(vsi, true) reaches this function from these paths:
ice_prepare_for_reset()
(non-PFR reset with ICE_FLAG_TC_MQPRIO set, in the service task)
ice_setup_tc_mqprio_qdisc()
(mqprio qdisc removal, under RTNL and tc_mutex)
ice_rebuild_channels()
(cleanup error path, with rem_adv_fltr)
After the first call, every later ice_add_cls_flower() and
ice_del_cls_flower() blocks forever in mutex_lock().
A second ice_rem_all_chnl_fltrs() call deadlocks on itself. One way to hit
this is to add an mqprio channel qdisc, delete it, add it again and delete
it again. The second delete then hangs while holding RTNL. If the second
call comes from ice_prepare_for_reset() instead, the reset never finishes.
ice_deinit_pf() would also end up calling mutex_destroy() on a mutex that
is still locked.
The race described in the commit message also looks like it is still
open. The walk, the ice_rem_adv_rule_by_id() calls, the num_chnl_fltr and
num_dmac_chnl_fltrs updates, hlist_del() and kfree() all still run without
the lock.
ice_del_cls_flower() can find a filter under the mutex while this loop
frees it:
ice_del_cls_flower()
fltr = ice_find_tc_flower_fltr(pf, cls_flower->cookie);
...
fltr->extack = cls_flower->common.extack;
err = ice_del_tc_fltr(vsi, fltr);
Could that write freed memory, or lead to a double hlist_del() and
kfree() of the same fltr?
The reverse also seems possible. The locked delete path can free the node
this unlocked safe iterator has already cached in node. A concurrent
hlist_add_head() in ice_add_cls_flower() can also corrupt the list during
the walk.
> diff --git a/drivers/net/ethernet/intel/ice/ice_tc_lib.c b/drivers/net/ethernet/intel/ice/ice_tc_lib.c
> index d20357c04127..955ad3cc5417 100644
> --- a/drivers/net/ethernet/intel/ice/ice_tc_lib.c
> +++ b/drivers/net/ethernet/intel/ice/ice_tc_lib.c
[ ... ]
> @@ -2340,10 +2356,12 @@ void ice_replay_tc_fltrs(struct ice_pf *pf)
> struct ice_tc_flower_fltr *fltr;
> struct hlist_node *node;
>
> + mutex_lock(&pf->tc_flower_fltr_lock);
> hlist_for_each_entry_safe(fltr, node,
> &pf->tc_flower_fltr_list,
> tc_flower_node) {
> fltr->extack = NULL;
> ice_add_switch_fltr(fltr->src_vsi, fltr);
> }
> + mutex_unlock(&pf->tc_flower_fltr_lock);
> }
[Severity: Low]
Does anything call ice_replay_tc_fltrs()?
The commit message says:
"and ice_replay_tc_fltrs() replays the same list after reset."
and lists among the consequences:
"stale filter state after replay"
The only references to ice_replay_tc_fltrs() in drivers/net/ethernet/intel/ice
are its definition and kernel-doc in ice_tc_lib.c and its prototype in
ice_tc_lib.h. Since nothing calls it, it does not run after reset and
cannot race with flower add or delete. The locking added here has no
effect.
Could the commit message be updated so it does not list
ice_replay_tc_fltrs() as a race participant or replay as a consequence?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261020033248.522973-1-18983559317%40163.com
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-22 22:34 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-20 3:32 [PATCH v1] ice: Lock TC flower filter list operations Binbin Deng
2026-10-22 22:34 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®