[PATCH net v2] nfp: flower: protect offloaded MAC lifetime and preserve its index

From: Hongyan Xu

Date: Sun Nov 15 2026 - 06:48:18 EST


The flower offload path uses nfp_fl_lock rather than RTNL. A MAC-table
lookup's internal RCU critical section ends before returning its entry,
while the RTNL notifier can remove that entry and immediately free it.

Add an rcu_head and introduce kfree_rcu() in both the deletion path and
the add rollback path: rollback can remove an entry already visible in
the hash. Hold an outer RCU read lock through copying the index, and
keep the sleeping firmware-message allocation outside it.

Apply the per-rule IPv6 bit to the local copy only. Writing that flag
back into the shared index races with the notifier and breaks the
global-index test that decides whether to release an IDA ID. The flow
already saves the payload index for deletion, so delete retains the
required IPv6 bit without modifying the table. Pair the snapshot's
READ_ONCE with WRITE_ONCE at the three RTNL-serialized index updates.

Found by the author's in-house static analysis tool.

Fixes: 14690995c141 ("nfp: flower: avoid rmmod nfp crash issues")
Fixes: 5c4f5e19d6a8 ("nfp: flower: add ipv6 bit to pre_tunnel control message")
Cc: stable@xxxxxxxxxxxxxxx
Link: https://lore.kernel.org/all/20261108105605.1668-1-getshell@xxxxxxxxxx/
Signed-off-by: Hongyan Xu <getshell@xxxxxxxxxx>
Signed-off-by: Zehao Gao <gaozehao@xxxxxxxxx>
---

Discovery: found by the author's in-house static analysis tool.

.../netronome/nfp/flower/tunnel_conf.c | 26 ++++++++++++-------
1 file changed, 17 insertions(+), 9 deletions(-)

diff --git a/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c b/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c
index 46b6cda..0b1f872 100644
--- a/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c
+++ b/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c
@@ -188,6 +188,7 @@ enum nfp_flower_mac_offload_cmd {
* @ref_count: Number of devs using this MAC address
* @repr_list: List of reprs sharing this MAC address
* @bridge_count: Number of bridge/internal devs with MAC
+ * @rcu: RCU head used when freeing the entry
*/
struct nfp_tun_offloaded_mac {
struct rhash_head ht_node;
@@ -196,6 +197,7 @@ struct nfp_tun_offloaded_mac {
int ref_count;
struct list_head repr_list;
int bridge_count;
+ struct rcu_head rcu;
};

static const struct rhashtable_params offloaded_macs_params = {
@@ -1136,7 +1138,7 @@ nfp_tunnel_add_shared_mac(struct nfp_app *app, struct net_device *netdev,
goto err_free_ida;
}

- entry->index = nfp_mac_idx;
+ WRITE_ONCE(entry->index, nfp_mac_idx);
nfp_tunnel_offloaded_macs_inc_ref_and_link(entry, netdev, mod);

return 0;
@@ -1145,7 +1147,7 @@ nfp_tunnel_add_shared_mac(struct nfp_app *app, struct net_device *netdev,
rhashtable_remove_fast(&priv->tun.offloaded_macs, &entry->ht_node,
offloaded_macs_params);
err_free_entry:
- kfree(entry);
+ kfree_rcu(entry, rcu);
err_free_ida:
if (ida_idx != -1)
ida_free(&priv->tun.mac_off_ids, ida_idx);
@@ -1188,7 +1190,7 @@ nfp_tunnel_del_shared_mac(struct nfp_app *app, struct net_device *netdev,
return 0;
}

- entry->index = nfp_mac_idx;
+ WRITE_ONCE(entry->index, nfp_mac_idx);
return 0;
}
}
@@ -1212,7 +1214,7 @@ nfp_tunnel_del_shared_mac(struct nfp_app *app, struct net_device *netdev,

ida_idx = nfp_tunnel_get_ida_from_global_mac_idx(entry->index);
ida_free(&priv->tun.mac_off_ids, ida_idx);
- entry->index = nfp_mac_idx;
+ WRITE_ONCE(entry->index, nfp_mac_idx);
return 0;
}

@@ -1234,7 +1236,7 @@ nfp_tunnel_del_shared_mac(struct nfp_app *app, struct net_device *netdev,
ida_free(&priv->tun.mac_off_ids, ida_idx);
}

- kfree(entry);
+ kfree_rcu(entry, rcu);

return __nfp_tunnel_offload_mac(app, mac, 0, true);
}
@@ -1426,6 +1428,7 @@ int nfp_flower_xmit_pre_tun_flow(struct nfp_app *app,
struct nfp_flower_meta_tci *key_meta;
struct nfp_tun_pre_tun_rule payload;
struct net_device *internal_dev;
+ u16 mac_idx;
int err;

if (app_priv->pre_tun_rule_cnt == NFP_TUN_PRE_TUN_RULE_LIMIT)
@@ -1441,21 +1444,26 @@ int nfp_flower_xmit_pre_tun_flow(struct nfp_app *app,
* Note that because the device is always an internal port, it will
* have a constant global index so does not need to be tracked.
*/
+ rcu_read_lock();
mac_entry = nfp_tunnel_lookup_offloaded_macs(app,
internal_dev->dev_addr);
- if (!mac_entry)
+ if (!mac_entry) {
+ rcu_read_unlock();
return -ENOENT;
+ }
+ mac_idx = READ_ONCE(mac_entry->index);
+ rcu_read_unlock();

/* Set/clear IPV6 bit. cpu_to_be16() swap will lead to MSB being
* set/clear for port_idx.
*/
key_meta = (struct nfp_flower_meta_tci *)flow->unmasked_data;
if (key_meta->nfp_flow_key_layer & NFP_FLOWER_LAYER_IPV6)
- mac_entry->index |= NFP_TUN_PRE_TUN_IPV6_BIT;
+ mac_idx |= NFP_TUN_PRE_TUN_IPV6_BIT;
else
- mac_entry->index &= ~NFP_TUN_PRE_TUN_IPV6_BIT;
+ mac_idx &= ~NFP_TUN_PRE_TUN_IPV6_BIT;

- payload.port_idx = cpu_to_be16(mac_entry->index);
+ payload.port_idx = cpu_to_be16(mac_idx);

/* Copy mac id and vlan to flow - dev may not exist at delete time. */
flow->pre_tun_rule.vlan_tci = payload.vlan_tci;
--
2.50.1.windows.1