summaryrefslogtreecommitdiff
path: root/net/core
diff options
context:
space:
mode:
authorEric Dumazet <edumazet@google.com>2026-08-12 08:54:38 +0000
committerJakub Kicinski <kuba@kernel.org>2026-08-17 10:27:48 -0700
commit21ef2d065ad3f0cfbf2ae51260bf962a9fa2c643 (patch)
treeffe5d117639fd24ef2082686cae6cc7d00e00071 /net/core
parente6a5d573d24cd375e09d24f136523cb3cc85c9d3 (diff)
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 'net/core')
-rw-r--r--net/core/dev.c46
1 files changed, 33 insertions, 13 deletions
diff --git a/net/core/dev.c b/net/core/dev.c
index 517ac6a575c4..694a5ab773f7 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -2652,11 +2652,13 @@ EXPORT_SYMBOL_GPL(dev_queue_xmit_nit);
*/
static void netif_setup_tc(struct net_device *dev, unsigned int txq)
{
+ struct netdev_tc_txq res;
int i;
- struct netdev_tc_txq *tc = &dev->tc_to_txq[0];
+
+ res.combined = READ_ONCE(dev->tc_to_txq[0].combined);
/* If TC0 is invalidated disable TC mapping */
- if (tc->offset + tc->count > txq) {
+ if (res.offset + res.count > txq) {
netdev_warn(dev, "Number of in use tx queues changed invalidating tc mappings. Priority traffic classification disabled!\n");
dev->num_tc = 0;
return;
@@ -2666,8 +2668,8 @@ static void netif_setup_tc(struct net_device *dev, unsigned int txq)
for (i = 1; i < TC_BITMASK + 1; i++) {
int q = netdev_get_prio_tc_map(dev, i);
- tc = &dev->tc_to_txq[q];
- if (tc->offset + tc->count > txq) {
+ res.combined = READ_ONCE(dev->tc_to_txq[q].combined);
+ if (res.offset + res.count > txq) {
netdev_warn(dev, "Number of in use tx queues changed. Priority %i to tc mapping %i is no longer valid. Setting map to 0\n",
i, q);
netdev_set_prio_tc_map(dev, i, 0);
@@ -2683,7 +2685,10 @@ int netdev_txq_to_tc(struct net_device *dev, unsigned int txq)
/* walk through the TCs and see if it falls into any of them */
for (i = 0; i < TC_MAX_QUEUE; i++, tc++) {
- if ((txq - tc->offset) < tc->count)
+ struct netdev_tc_txq res;
+
+ res.combined = READ_ONCE(tc->combined);
+ if ((txq - res.offset) < res.count)
return i;
}
@@ -3103,6 +3108,8 @@ static void netdev_unbind_all_sb_channels(struct net_device *dev)
void netdev_reset_tc(struct net_device *dev)
{
+ int i;
+
#ifdef CONFIG_XPS
netif_reset_xps_queues_gt(dev, 0);
#endif
@@ -3110,21 +3117,26 @@ void netdev_reset_tc(struct net_device *dev)
/* Reset TC configuration of device */
dev->num_tc = 0;
- memset(dev->tc_to_txq, 0, sizeof(dev->tc_to_txq));
+ for (i = 0; i < TC_MAX_QUEUE; i++)
+ WRITE_ONCE(dev->tc_to_txq[i].combined, 0);
memset(dev->prio_tc_map, 0, sizeof(dev->prio_tc_map));
}
EXPORT_SYMBOL(netdev_reset_tc);
int netdev_set_tc_queue(struct net_device *dev, u8 tc, u16 count, u16 offset)
{
+ struct netdev_tc_txq res = {
+ .count = count,
+ .offset = offset,
+ };
+
if (tc >= dev->num_tc)
return -EINVAL;
#ifdef CONFIG_XPS
netif_reset_xps_queues(dev, offset, count);
#endif
- dev->tc_to_txq[tc].count = count;
- dev->tc_to_txq[tc].offset = offset;
+ WRITE_ONCE(dev->tc_to_txq[tc].combined, res.combined);
return 0;
}
EXPORT_SYMBOL(netdev_set_tc_queue);
@@ -3148,11 +3160,13 @@ void netdev_unbind_sb_channel(struct net_device *dev,
struct net_device *sb_dev)
{
struct netdev_queue *txq = &dev->_tx[dev->num_tx_queues];
+ int i;
#ifdef CONFIG_XPS
netif_reset_xps_queues_gt(sb_dev, 0);
#endif
- memset(sb_dev->tc_to_txq, 0, sizeof(sb_dev->tc_to_txq));
+ for (i = 0; i < TC_MAX_QUEUE; i++)
+ WRITE_ONCE(sb_dev->tc_to_txq[i].combined, 0);
memset(sb_dev->prio_tc_map, 0, sizeof(sb_dev->prio_tc_map));
while (txq-- != &dev->_tx[0]) {
@@ -3175,8 +3189,12 @@ int netdev_bind_sb_channel_queue(struct net_device *dev,
return -EINVAL;
/* Record the mapping */
- sb_dev->tc_to_txq[tc].count = count;
- sb_dev->tc_to_txq[tc].offset = offset;
+ struct netdev_tc_txq res = {
+ .count = count,
+ .offset = offset,
+ };
+
+ WRITE_ONCE(sb_dev->tc_to_txq[tc].combined, res.combined);
/* Provide a way for Tx queue to find the tc_to_txq map or
* XPS map for itself.
@@ -3542,9 +3560,11 @@ static u16 skb_tx_hash(const struct net_device *dev,
if (dev->num_tc) {
u8 tc = netdev_get_prio_tc_map(dev, skb->priority);
+ struct netdev_tc_txq res;
- qoffset = sb_dev->tc_to_txq[tc].offset;
- qcount = sb_dev->tc_to_txq[tc].count;
+ res.combined = READ_ONCE(sb_dev->tc_to_txq[tc].combined);
+ qoffset = res.offset;
+ qcount = res.count;
if (unlikely(!qcount)) {
net_warn_ratelimited("%s: invalid qcount, qoffset %u for tc %u\n",
sb_dev->name, qoffset, tc);