diff options
| author | Eric Dumazet <edumazet@google.com> | 2026-08-12 08:54:38 +0000 |
|---|---|---|
| committer | Jakub Kicinski <kuba@kernel.org> | 2026-08-17 10:27:48 -0700 |
| commit | 21ef2d065ad3f0cfbf2ae51260bf962a9fa2c643 (patch) | |
| tree | ffe5d117639fd24ef2082686cae6cc7d00e00071 /drivers | |
| parent | e6a5d573d24cd375e09d24f136523cb3cc85c9d3 (diff) | |
| download | linux-21ef2d065ad3f0cfbf2ae51260bf962a9fa2c643.tar.gz linux-21ef2d065ad3f0cfbf2ae51260bf962a9fa2c643.zip | |
net: prevent torn reads in netdev_tc_txq
netdev_set_tc_queue() (and related helpers/drivers such as
netdev_bind_sb_channel_queue(), netdev_reset_tc(), and
netdev_unbind_sb_channel()) perform separate 16-bit writes to
dev->tc_to_txq[tc].count and dev->tc_to_txq[tc].offset.
Furthermore, memset() in netdev_reset_tc() and
netdev_unbind_sb_channel() provides no guarantee of performing
full 32-bit word stores.
Concurrent lockless readers (e.g. skb_tx_hash(), netdev_txq_to_tc(),
ixgbe_select_queue(), taprio, mqprio, FPE drivers) can observe torn
values where offset and count belong to inconsistent configurations.
Redefine struct netdev_tc_txq to embed count and offset inside a union
with a u32 combined field, allowing atomic manipulation via
READ_ONCE() and WRITE_ONCE().
Update all lockless readers and writers across the kernel to use
READ_ONCE() and WRITE_ONCE() on the combined field.
Signed-off-by: Eric Dumazet <edumazet@google.com>
Link: https://patch.msgid.link/20260812085440.3917924-2-edumazet@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/net/ethernet/intel/igc/igc_tsn.c | 6 | ||||
| -rw-r--r-- | drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 7 | ||||
| -rw-r--r-- | drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 2 | ||||
| -rw-r--r-- | drivers/net/ethernet/sfc/falcon/tx.c | 8 | ||||
| -rw-r--r-- | drivers/net/ethernet/sfc/siena/tx.c | 8 | ||||
| -rw-r--r-- | drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c | 14 |
6 files changed, 31 insertions, 14 deletions
diff --git a/drivers/net/ethernet/intel/igc/igc_tsn.c b/drivers/net/ethernet/intel/igc/igc_tsn.c index 52de2bcbadbe..0c08650d3bb2 100644 --- a/drivers/net/ethernet/intel/igc/igc_tsn.c +++ b/drivers/net/ethernet/intel/igc/igc_tsn.c @@ -183,13 +183,15 @@ static u32 igc_fpe_map_preempt_tc_to_queue(const struct igc_adapter *adapter, u32 i, queue = 0; for (i = 0; i < dev->num_tc; i++) { + struct netdev_tc_txq res; u32 offset, count; if (!(preemptible_tcs & BIT(i))) continue; - offset = dev->tc_to_txq[i].offset; - count = dev->tc_to_txq[i].count; + res.combined = READ_ONCE(dev->tc_to_txq[i].combined); + offset = res.offset; + count = res.count; queue |= GENMASK(offset + count - 1, offset); } diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c index 8873a8cc4a18..f91856498eb2 100644 --- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c @@ -9273,10 +9273,11 @@ static u16 ixgbe_select_queue(struct net_device *dev, struct sk_buff *skb, if (sb_dev) { u8 tc = netdev_get_prio_tc_map(dev, skb->priority); struct net_device *vdev = sb_dev; + struct netdev_tc_txq res; - txq = vdev->tc_to_txq[tc].offset; - txq += reciprocal_scale(skb_get_hash(skb), - vdev->tc_to_txq[tc].count); + res.combined = READ_ONCE(vdev->tc_to_txq[tc].combined); + txq = res.offset; + txq += reciprocal_scale(skb_get_hash(skb), res.count); return txq; } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index ca3d7c6b5210..8a877891e690 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -3247,7 +3247,7 @@ static int mlx5e_update_tc_and_tx_queues(struct mlx5e_priv *priv) old_num_txqs = netdev->real_num_tx_queues; old_ntc = netdev->num_tc ? : 1; for (i = 0; i < ARRAY_SIZE(old_tc_to_txq); i++) - old_tc_to_txq[i] = netdev->tc_to_txq[i]; + old_tc_to_txq[i].combined = READ_ONCE(netdev->tc_to_txq[i].combined); nch = priv->channels.params.num_channels; ntc = priv->channels.params.mqprio.num_tc; diff --git a/drivers/net/ethernet/sfc/falcon/tx.c b/drivers/net/ethernet/sfc/falcon/tx.c index 9e18aaf44bad..e4d47d26a87a 100644 --- a/drivers/net/ethernet/sfc/falcon/tx.c +++ b/drivers/net/ethernet/sfc/falcon/tx.c @@ -439,8 +439,12 @@ int ef4_setup_tc(struct net_device *net_dev, enum tc_setup_type type, return 0; for (tc = 0; tc < num_tc; tc++) { - net_dev->tc_to_txq[tc].offset = tc * efx->n_tx_channels; - net_dev->tc_to_txq[tc].count = efx->n_tx_channels; + struct netdev_tc_txq res = { + .offset = tc * efx->n_tx_channels, + .count = efx->n_tx_channels, + }; + + WRITE_ONCE(net_dev->tc_to_txq[tc].combined, res.combined); } if (num_tc > net_dev->num_tc) { diff --git a/drivers/net/ethernet/sfc/siena/tx.c b/drivers/net/ethernet/sfc/siena/tx.c index 91e87594ed1e..1ce98f8fdaf8 100644 --- a/drivers/net/ethernet/sfc/siena/tx.c +++ b/drivers/net/ethernet/sfc/siena/tx.c @@ -380,8 +380,12 @@ int efx_siena_setup_tc(struct net_device *net_dev, enum tc_setup_type type, return 0; for (tc = 0; tc < num_tc; tc++) { - net_dev->tc_to_txq[tc].offset = tc * efx->n_tx_channels; - net_dev->tc_to_txq[tc].count = efx->n_tx_channels; + struct netdev_tc_txq res = { + .offset = tc * efx->n_tx_channels, + .count = efx->n_tx_channels, + }; + + WRITE_ONCE(net_dev->tc_to_txq[tc].combined, res.combined); } net_dev->num_tc = num_tc; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c index c54c70224351..c889204a7aa5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c @@ -217,8 +217,11 @@ int dwmac5_fpe_map_preemption_class(struct net_device *ndev, * and is direct one-to-one mapping." */ for (u32 tc = 0; tc < num_tc; tc++) { - count = ndev->tc_to_txq[tc].count; - offset = ndev->tc_to_txq[tc].offset; + struct netdev_tc_txq res; + + res.combined = READ_ONCE(ndev->tc_to_txq[tc].combined); + count = res.count; + offset = res.offset; if (pclass & BIT(tc)) preemptible_txqs |= GENMASK(offset + count - 1, offset); @@ -275,8 +278,11 @@ int dwxgmac3_fpe_map_preemption_class(struct net_device *ndev, * any of the scheduling algorithms." */ for (u32 tc = 0; tc < num_tc; tc++) { - count = ndev->tc_to_txq[tc].count; - offset = ndev->tc_to_txq[tc].offset; + struct netdev_tc_txq res; + + res.combined = READ_ONCE(ndev->tc_to_txq[tc].combined); + count = res.count; + offset = res.offset; if (pclass & BIT(tc)) preemptible_txqs |= GENMASK(offset + count - 1, offset); |
